diff --git a/README.en.md b/README.en.md index 4ed7873..d3a479a 100644 --- a/README.en.md +++ b/README.en.md @@ -5,7 +5,7 @@ author JDK Spring Boot - LICENSE + LICENSE

@@ -20,17 +20,12 @@ ## Introduction -`spring boot demo` is a project for learning and practicing `spring boot`, including `66` demos, and `54` of them have been done. +`spring boot demo` is a project for learning and practicing `spring boot`, including `66` demos, and `55` of them have been done. -This project has integrated actuator (`monitoring`), admin (`visual monitoring`), logback (`log`), aopLog (`recording web request logs through AOP`), global exception handling (`json level and page level` ), freemarker (`template engine`), thymeleaf (`template engine`), Beetl (`template engine`), Enjoy (`template engine`), JdbcTemplate (`general JDBC operate database`), JPA (`powerful ORM framework `), mybatis (`powerful ORM framework`), Generic Mapper (`mybatis quick operation `), PageHelper (`powerful mybatis pagination plugin`), mybatis-plus (`mybatis quick operation`), BeetlSQL (`powerful ORM framework `), upload (`local file upload and qiniu cloud file upload`), redis (`cache`), ehcache (`cache`), email (`send various types of mail`), task (`basic scheduled tasks`), quartz (`dynamic management scheduled tasks`), xxl-job (`distributed scheduled tasks`), swagger (`API interface management and tests`), security (`RBAC-based Dynamic Rights Authentication`), SpringSession (`session sharing`), Zookeeper (`implement distributed locks by AOP`), RabbitMQ (`message queue`), Kafka (`message queue`), websocket (` server pushes the monitoring server status to front end `), socket.io (`chat room`), ureport2 (`Chinese-style report`), packaged into a `war` file, integrate ElasticSearch (`basic operations and advanced queries`), Async ( `asynchronous tasks`), integrated Dubbo (`with official starter`), MongoDB (`document database`), neo4j (`graph database`), docker (`container`), `JPA Multi-Datasource`, `Mybatis Multi-Datasource`, `code generator`', GrayLog (`log collection`), JustAuth (`third-party login`), LDAP(`CURD`), `Dynamically add/switch datasources`, Standalone RateLimiting(`AOP + Guava RateLimiter`), Distributed Ratelimiting(`AOP + Redis + Lua`), ElasticSearch 7.x(`use official Rest High Level Client`), HTTPS, Flyway(`initialize databases`). +This project has integrated actuator (`monitoring`), admin (`visual monitoring`), logback (`log`), aopLog (`recording web request logs through AOP`), global exception handling (`json level and page level` ), freemarker (`template engine`), thymeleaf (`template engine`), Beetl (`template engine`), Enjoy (`template engine`), JdbcTemplate (`general JDBC operate database`), JPA (`powerful ORM framework `), mybatis (`powerful ORM framework`), Generic Mapper (`mybatis quick operation `), PageHelper (`powerful mybatis pagination plugin`), mybatis-plus (`mybatis quick operation`), BeetlSQL (`powerful ORM framework `), upload (`local file upload and qiniu cloud file upload`), redis (`cache`), ehcache (`cache`), email (`send various types of mail`), task (`basic scheduled tasks`), quartz (`dynamic management scheduled tasks`), xxl-job (`distributed scheduled tasks`), swagger (`API interface management and tests`), security (`RBAC-based Dynamic Rights Authentication`), SpringSession (`session sharing`), Zookeeper (`implement distributed locks by AOP`), RabbitMQ (`message queue`), Kafka (`message queue`), websocket (` server pushes the monitoring server status to front end `), socket.io (`chat room`), ureport2 (`Chinese-style report`), packaged into a `war` file, integrate ElasticSearch (`basic operations and advanced queries`), Async ( `asynchronous tasks`), integrated Dubbo (`with official starter`), MongoDB (`document database`), neo4j (`graph database`), docker (`container`), `JPA Multi-Datasource`, `Mybatis Multi-Datasource`, `code generator`', GrayLog (`log collection`), JustAuth (`third-party login`), LDAP(`CURD`), `Dynamically add/switch datasources`, Standalone RateLimiting(`AOP + Guava RateLimiter`), Distributed Ratelimiting(`AOP + Redis + Lua`), ElasticSearch 7.x(`use official Rest High Level Client`), HTTPS, Flyway(`initialize databases`),UReport2(`Chinese complex report `). > If you have demos to contribute or needs to meet, it is very welcome to submit a [issue](https://github.com/xkcoding/spring-boot-demo/issues/new) and I will add it to my [TODO](./TODO.en.md) list. -## Thanks - -- jetbrains**Thanks JetBrains Offer Open Source Free License** -- [Thanks MyBatisCodeHelper-Pro(The Best Code Generator Plugin) Offer Permanent Activation Code](https://gejun123456.github.io/MyBatisCodeHelper-Pro/#/?id=mybatiscodehelper-pro) - ## Branch Introduction - branch master: Based on Spring Boot version `2.1.0.RELEASE`. Every module's parent dependency is the pom.xml at root directory in convenience of managing common dependencies and learning spring boot. @@ -55,318 +50,12 @@ This project has integrated actuator (`monitoring`), admin (`visual monitoring`) 6. **`Note: Each demo has a detailed README file. Remember to check it before running the demo~`** 7. **`Note: In some condition you have to execute sql to prepare data before running demo, don't forget it~`** -## TODO - -View the [TODO](./TODO.en.md) file - -## Introduction of each Module - -| Module Name | Module Description | -| ------------------------------------------------------------ | ------------------------------------------------------------ | -| [spring-boot-demo-helloworld](./spring-boot-demo-helloworld) | a helloworld demo. | -| [spring-boot-demo-properties](./spring-boot-demo-properties) | a demo to read the contents of configuration file. | -| [spring-boot-demo-actuator](./spring-boot-demo-actuator) | a demo to integrate spring-boot-starter-actuator for monitoring the starting status and the running status of application. | -| [spring-boot-demo-admin-client](./spring-boot-demo-admin/spring-boot-demo-admin-client) | a client demo to integrate spring-boot-admin for visually monitoring the running status of application, it can be used with spring-boot-starter-actuator. | -| [spring-boot-demo-admin-server](./spring-boot-demo-admin/spring-boot-demo-admin-server) | a server demo to integrate spring-boot-admin for visually monitoring the running status of the spring-boot program, it can be used with spring-boot-starter-actuator. | -| [spring-boot-demo-logback](./spring-boot-demo-logback) | a demo to integrate the logback for logging. | -| [spring-boot-demo-log-aop](./spring-boot-demo-log-aop) | a demo to record web request logs using AOP aspect. | -| [spring-boot-demo-exception-handler](./spring-boot-demo-exception-handler) | a demo to demonstrate global exception handling, including 2 types, the first one returns json data, and the second one jumps to error page. | -| [spring-boot-demo-template-freemarker](./spring-boot-demo-template-freemarker) | a demo to integrate Freemarker template engine. | -| [spring-boot-demo-template-thymeleaf](./spring-boot-demo-template-thymeleaf) | a demo to integrate Thymeleaf template engine. | -| [spring-boot-demo-template-beetl](./spring-boot-demo-template-beetl) | a demo to integrate Beetl template engine. | -| [spring-boot-demo-template-enjoy](./spring-boot-demo-template-enjoy) | a demo to integrate Enjoy template engine. | -| [spring-boot-demo-orm-jdbctemplate](./spring-boot-demo-orm-jdbctemplate) | a demo to integrate the Jdbc Template for operating database and easily encapsulate the generic Dao layer. | -| [spring-boot-demo-orm-jpa](./spring-boot-demo-orm-jpa) | a demo to integrate spring-boot-starter-data-jpa for operating database. | -| [spring-boot-demo-orm-mybatis](./spring-boot-demo-orm-mybatis) | a demo to integrate native mybatis by using [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter) dependency. | -| [spring-boot-demo-orm-mybatis-mapper-page](./spring-boot-demo-orm-mybatis-mapper-page) | a demo to integrate [Mapper](https://github.com/abel533/Mapper) and [PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) by using [mapper-spring-boot-starter](https://github.com/abel533/Mapper/tree/master/spring-boot-starter) and [pagehelper-spring-boot-starter](https://github.com/pagehelper/pagehelper-spring-boot) dependencies. | -| [spring-boot-demo-orm-mybatis-plus](./spring-boot-demo-orm-mybatis-plus) | a demo to integrate [mybatis-plus](https://mybatis.plus/en/) by using [mybatis-plus-boot-starter](http://mp.baomidou.com/) dependency, integrate BaseMapper / BaseService / ActiveRecord to operate database. | -| [spring-boot-demo-orm-beetlsql](./spring-boot-demo-orm-beetlsql) | a demo to integrate [beetl-sql](http://ibeetl.com/guide/#beetlsql) by using [beetl-framework-starter](http://ibeetl.com/guide/#beetlsql) dependency. | -| [spring-boot-demo-upload](./spring-boot-demo-upload) | a file upload demo, including local file upload and qiniu cloud file upload. | -| [spring-boot-demo-cache-redis](./spring-boot-demo-cache-redis) | a demo to integrate redis, operate data in redis, and use redis to cache data. | -| [spring-boot-demo-cache-ehcache](./spring-boot-demo-cache-ehcache) | a demo to integrate ehcache, and use ehcache to cache data. | -| [spring-boot-demo-email](./spring-boot-demo-email) | a demo to integrate email, including sending simple text email, HTML email (including template HTML email), attachment email, and static resource email. | -| [spring-boot-demo-task](./spring-boot-demo-task) | a demo to show easy to use scheduled task. | -| [spring-boot-demo-task-quartz](./spring-boot-demo-task-quartz) | a demo to integrate quartz for managing scheduled tasks, including adding new scheduled tasks, deleting scheduled tasks, suspending scheduled tasks, restoring scheduled tasks, modifying scheduled task startup times, and timing task list queries, and `providing front-end pages`. | -| [spring-boot-demo-task-xxl-job](./spring-boot-demo-task-xxl-job) | a demo to integrate [xxl-job](http://www.xuxueli.com/xxl-job/en/#/) for distributed scheduled tasks and provide methods to manage scheduled tasks bypass `xxl-job-admin`, including scheduled task lists, trigger lists, new scheduled tasks, deleted scheduled tasks, stopped scheduled tasks, and started scheduled tasks. Modify the scheduled task and manually trigger the scheduled task. | -| [spring-boot-demo-swagger](./spring-boot-demo-swagger) | a demo to integrate native `swagger` to manage and test API interfaces. | -| [spring-boot-demo-swagger-beauty](./spring-boot-demo-swagger-beauty) | a demo to integrate third part of swagger dependency [swagger-bootstrap-ui](https://github.com/xiaoymin/Swagger-Bootstrap-UI) to beautify document style and manage and test API interfaces. | -| [spring-boot-demo-rbac-security](./spring-boot-demo-rbac-security) | a demo to integrate spring security implement privilege management based on RBAC privilege model, supports custom filtering request, dynamic privilege authentication, uses JWT security authentication, supports online population statistics, manually kicks out users, etc. | -| [spring-boot-demo-rbac-shiro](./spring-boot-demo-rbac-shiro) | NOT FINISHED YET!
a demo to integrate shiro for authentication management. | -| [spring-boot-demo-session](./spring-boot-demo-session) | a demo to integrate Spring Session to implement Session sharing, restart program Session does not expire. | -| [spring-boot-demo-oauth](./spring-boot-demo-oauth) | NOT FINISHED YET!
a demo to implement the oauth server and to implement oauth2 protocol such as the authorization code, access token. | -| [spring-boot-demo-social](./spring-boot-demo-social) | a demo to integrate third-party login by using `justauth-spring-boot-starter` dependency to achieve QQ login, GitHub login, WeChat login, Google login, Microsoft login, Xiaomi login, enterprise WeChat login. | -| [spring-boot-demo-zookeeper](./spring-boot-demo-zookeeper) | a demo to integrate Zookeeper and AOP to implement distributed lock. | -| [spring-boot-demo-mq-rabbitmq](./spring-boot-demo-mq-rabbitmq) | a demo to integrate RabbitMQ implementation for message delivery and reception based on direct queue mode, fanout mode, topic mode, delay queue. | -| [spring-boot-demo-mq-rocketmq](./spring-boot-demo-mq-rocketmq) | NOT FINISHED YET!
a demo to integrate RocketMQ implementation for message delivery and reception. | -| [spring-boot-demo-mq-kafka](./spring-boot-demo-mq-kafka) | a demo to integrate Kafka implementation for message delivery and reception. | -| [spring-boot-demo-websocket](./spring-boot-demo-websocket) | a demo to integrate websocket, the backend actively pushes the server running status to front end. | -| [spring-boot-demo-websocket-socketio](./spring-boot-demo-websocket-socketio) | a demo to integrate websocket by using `netty-socketio`, implement a simple chat room. | -| [spring-boot-demo-ureport2](./spring-boot-demo-ureport2) | NOT FINISHED YET!
a demo to integrate [ureport2](https://github.com/youseries/ureport) to implement complex, customized Chinese-style reports. | -| [spring-boot-demo-uflo](./spring-boot-demo-uflo) | NOT FINISHED YET!
a demo to integrate [uflo](https://github.com/youseries/uflo)(process engine like Activiti and Flowable) to quickly implement a lightweight process engine. | -| [spring-boot-demo-urule](./spring-boot-demo-urule) | NOT FINISHED YET!
a demo to integrate [urule](https://github.com/youseries/urule)(rule engine like drools) fast implementation rule engine. | -| [spring-boot-demo-activiti](./spring-boot-demo-activiti) | NOT FINISHED YET!
a demo to integrate Activiti 7 process engine. | -| [spring-boot-demo-async](./spring-boot-demo-async) | asynchronous execution of tasks by using natively provided asynchronous task support. | -| [spring-boot-demo-war](./spring-boot-demo-war) | packaged into a war format configuration | -| [spring-boot-demo-elasticsearch](./spring-boot-demo-elasticsearch) | a demo to integrate ElasticSearch by using `spring-boot-starter-data-elasticsearch` to implement advanced techniques for using ElasticSearch, including creating indexes, configuring mappings, deleting indexes, adding and deleting basic operations, complex queries, advanced queries, aggregate queries, etc. | -| [spring-boot-demo-dubbo](./spring-boot-demo-dubbo) | a demo to integrate Dubbo, common module `spring-boot-demo-dubbo-common`, service provider `spring-boot-demo-dubbo-provider`, service consumer `spring-boot-demo-dubbo-consumer`. | -| [spring-boot-demo-mongodb](./spring-boot-demo-mongodb) | a demo to integrate MongoDB and use the official starter to CRUD. | -| [spring-boot-demo-neo4j](./spring-boot-demo-neo4j) | a demo to integrate Neo4j graph database to implement a campus character relationship network. | -| [spring-boot-demo-docker](./spring-boot-demo-docker) | docker container. | -| [spring-boot-demo-multi-datasource-jpa](./spring-boot-demo-multi-datasource-jpa) | a demo to implement JPA multi-datasource. | -| [spring-boot-demo-multi-datasource-mybatis](./spring-boot-demo-multi-datasource-mybatis) | a demo to implement Mybatis multi-datasource by using an open source solution from Mybatis-Plus. | -| [spring-boot-demo-sharding-jdbc](./spring-boot-demo-sharding-jdbc) | a demo to use `sharding-jdbc` to implement sub-database and sub-tables, while ORM uses Mybatis-Plus. | -| [spring-boot-demo-tio](./spring-boot-demo-tio) | NOT FINISHED YET!
a demo to integrate t-io(a network programming framework like netty). | -| [spring-boot-demo-grpc](./spring-boot-demo-grpc) | NOT FINISHED YET!
a demo to integrate Google grpc, need to be configure tls/ssl, see [ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5). | -| [spring-boot-demo-codegen](./spring-boot-demo-codegen) | a demo to integrate velocity template engine to implement code generator, improve development efficiency. | -| [spring-boot-demo-graylog](./spring-boot-demo-graylog) | a demo to integrate graylog for unified log collection. | -| spring-boot-demo-sso | NOT FINISHED YET!
a demo to integrate Single Sign On, see [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12). | -| [spring-boot-demo-ldap](./spring-boot-demo-ldap) | a demo to integrate LDAP to use `spring-boot-starter-data-ldap` to implement CURD operations and give the login demo, see [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23), thanks [@fxbin](https://github.com/fxbin). | -| [spring-boot-demo-dynamic-datasource](./spring-boot-demo-dynamic-datasource) | a demo to add datasource dynamically, switch datasource dynamically. | -| [spring-boot-demo-ratelimit-guava](./spring-boot-demo-ratelimit-guava) | a demo to use use Guava RateLimiter to protect API by standalone rate limiting. | -| [spring-boot-demo-ratelimit-redis](./spring-boot-demo-ratelimit-redis) | a demo to use Redis and Lua script implementation to protect API by distributed rate limiting. | -| [spring-boot-demo-https](./spring-boot-demo-https) | a demo to integrate HTTPS. | -| [spring-boot-demo-elasticsearch-rest-high-level-client](./spring-boot-demo-elasticsearch-rest-high-level-client) | a demo to integrate ElasticSearch 7.x version by using official Rest High Level Client to operate ES data. | -| [spring-boot-demo-flyway](./spring-boot-flyway) | a demo to integrate Flyway to initialize tables and data in database, Flyway also support the sql script version control. | - -## License - -[MIT](http://opensource.org/licenses/MIT) - -Copyright (c) 2018 Yangkai.Shen - ## Stargazers over time [![Stargazers over time](https://starchart.cc/xkcoding/spring-boot-demo.svg)](https://starchart.cc/xkcoding/spring-boot-demo) ## Appendix -### Pom.xml in the root directory - -```xml - - - - 4.0.0 - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - spring-boot-demo-helloworld - spring-boot-demo-properties - spring-boot-demo-actuator - spring-boot-demo-admin - spring-boot-demo-logback - spring-boot-demo-log-aop - spring-boot-demo-exception-handler - spring-boot-demo-template-freemarker - spring-boot-demo-template-thymeleaf - spring-boot-demo-template-beetl - spring-boot-demo-template-enjoy - spring-boot-demo-orm-jdbctemplate - spring-boot-demo-orm-jpa - spring-boot-demo-orm-mybatis - spring-boot-demo-orm-mybatis-mapper-page - spring-boot-demo-orm-mybatis-plus - spring-boot-demo-orm-beetlsql - spring-boot-demo-upload - spring-boot-demo-cache-redis - spring-boot-demo-cache-ehcache - spring-boot-demo-email - spring-boot-demo-task - spring-boot-demo-task-quartz - spring-boot-demo-task-xxl-job - spring-boot-demo-swagger - spring-boot-demo-swagger-beauty - spring-boot-demo-rbac-security - spring-boot-demo-rbac-shiro - spring-boot-demo-session - spring-boot-demo-oauth - spring-boot-demo-social - spring-boot-demo-zookeeper - spring-boot-demo-mq-rabbitmq - spring-boot-demo-mq-rocketmq - spring-boot-demo-mq-kafka - spring-boot-demo-websocket - spring-boot-demo-websocket-socketio - spring-boot-demo-ureport2 - spring-boot-demo-uflo - spring-boot-demo-urule - spring-boot-demo-activiti - spring-boot-demo-async - spring-boot-demo-dubbo - spring-boot-demo-war - spring-boot-demo-elasticsearch - spring-boot-demo-mongodb - spring-boot-demo-neo4j - spring-boot-demo-docker - spring-boot-demo-multi-datasource-jpa - spring-boot-demo-multi-datasource-mybatis - spring-boot-demo-sharding-jdbc - spring-boot-demo-tio - spring-boot-demo-codegen - spring-boot-demo-graylog - spring-boot-demo-ldap - spring-boot-demo-dynamic-datasource - spring-boot-demo-ratelimit-guava - spring-boot-demo-ratelimit-redis - spring-boot-demo-elasticsearch-rest-high-level-client - spring-boot-demo-https - spring-boot-demo-flyway - - pom - - spring-boot-demo - http://xkcoding.com - - - UTF-8 - UTF-8 - 1.8 - 1.8 - 1.8 - 2.1.0.RELEASE - 8.0.12 - 5.0.0 - 28.1-jre - 1.20 - - - - - aliyun - aliyun - https://maven.aliyun.com/repository/public - - true - - - false - - - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - mysql - mysql-connector-java - ${mysql.version} - - - - cn.hutool - hutool-all - ${hutool.version} - - - - com.google.guava - guava - ${guava.version} - - - - eu.bitwalker - UserAgentUtils - ${user.agent.version} - - - - - - - - - maven-clean-plugin - 3.0.0 - - - maven-resources-plugin - 3.0.2 - - - maven-compiler-plugin - 3.7.0 - - - maven-surefire-plugin - 2.20.1 - - - maven-jar-plugin - 3.0.2 - - - maven-install-plugin - 2.5.2 - - - maven-deploy-plugin - 2.8.2 - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - - - -``` - -### Official starter introduction - -| Name | Description | -| :------------------------------------- | :----------------------------------------------------------- | -| spring-boot-starter | The core Spring Boot starter, including auto-configuration support, logging and YAML. | -| spring-boot-starter-actuator | Production ready features to help you monitor and manage your application. | -| spring-boot-starter-amqp | Support for RabbitMQ messages | -| spring-boot-starter-aop | Support for aspect-oriented programming including spring-aop and AspectJ. | -| spring-boot-starter-batch | Support for “Spring Batch” including HSQLDB database. | -| spring-boot-starter-cache | Support for Spring’s Cache abstraction. | -| spring-boot-starter-data-elasticsearch | Support for the Elasticsearch search and analytics engine including spring-data-elasticsearch. | -| spring-boot-starter-data-jpa | Support for the “Java Persistence API” including spring-data-jpa, spring-orm and Hibernate. | -| spring-boot-starter-data-mongodb | Support for the MongoDB NoSQL Database, including spring-data-mongodb. | -| spring-boot-starter-data-rest | Support for exposing Spring Data repositories over REST via spring-data-rest-webmvc. | -| spring-boot-starter-data-solr | Support for the Apache Solr search platform, including spring-data-solr. | -| spring-boot-starter-freemarker | Support for the FreeMarker templating engine. | -| spring-boot-starter-groovy-templates | Support for the Groovy templating engine. | -| spring-boot-starter-integration | Support for common spring-integration modules. | -| spring-boot-starter-jdbc | Support for JDBC databases. | -| spring-boot-starter-jersey | Support for the Jersey RESTful Web Services framework. | -| spring-boot-starter-jta-atomikos | Support for JTA distributed transactions via Atomikos. | -| spring-boot-starter-jta-bitronix | Support for JTA distributed transactions via Bitronix. | -| spring-boot-starter-mail | Support for javax.mail. | -| spring-boot-starter-mustache | Support for the Mustache templating engine. | -| spring-boot-starter-redis | Support for the REDIS key-value data store, including spring-redis. | -| spring-boot-starter-security | Support for spring-security. | -| spring-boot-starter-social-facebook | Support for spring-social-facebook. | -| spring-boot-starter-social-linkedin | Support for spring-social-linkedin. | -| spring-boot-starter-social-twitter | Support for spring-social-twitter. | -| spring-boot-starter-test | Support for common test dependencies, including JUnit, Hamcrest and Mockito along with the spring-test module. | -| spring-boot-starter-thymeleaf | Support for the Thymeleaf templating engine, including integration with Spring. | -| spring-boot-starter-velocity | Support for the Velocity templating engine. | -| spring-boot-starter-web | Support for full-stack web development, including Tomcat and spring-webmvc. | -| spring-boot-starter-websocket | Support for WebSocket development. | -| spring-boot-starter-ws | Support for Spring Web Services. | - ### Recommended Open source - `JustAuth`:The most comprehensive open source library for third-party logins in history,https://github.com/justauth/JustAuth @@ -375,6 +64,87 @@ Copyright (c) 2018 Yangkai.Shen - `SpringBlade`:Complete micro-service online solution (required for enterprise development),https://github.com/chillzhuang/SpringBlade - `Pig`:The universe's strongest micro-service certification authorized scaffolding (architect necessary),https://github.com/pigxcloud/pig -### Advertisement +### TODO + +View the [TODO](./TODO.en.md) file + +### Introduction of each Module -[![JD_CLOUD](assets/jdcloud.jpg)](https://re.jdcloud.com/cps?returnUrl=aHR0cHM6Ly93d3cuamRjbG91ZC5jb20vY24vYWN0aXZpdHkveWVhci1lbmQ_bUlkPTE4JmNwc0tleT1iMjg2Y2Q0ZmExMWM0ODZhODU2NmUwNjc5MGQ0MzY4MA==) +| Module Name | Module Description | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [demo-helloworld](./demo-helloworld) | a helloworld demo. | +| [demo-properties](./demo-properties) | a demo to read the contents of configuration file. | +| [demo-actuator](./demo-actuator) | a demo to integrate spring-boot-starter-actuator for monitoring the starting status and the running status of application. | +| [demo-admin-client](./demo-admin/admin-client) | a client demo to integrate spring-boot-admin for visually monitoring the running status of application, it can be used with spring-boot-starter-actuator. | +| [demo-admin-server](./demo-admin/admin-server) | a server demo to integrate spring-boot-admin for visually monitoring the running status of the spring-boot program, it can be used with spring-boot-starter-actuator. | +| [demo-logback](./demo-logback) | a demo to integrate the logback for logging. | +| [demo-log-aop](./demo-log-aop) | a demo to record web request logs using AOP aspect. | +| [demo-exception-handler](./demo-exception-handler) | a demo to demonstrate global exception handling, including 2 types, the first one returns json data, and the second one jumps to error page. | +| [demo-template-freemarker](./demo-template-freemarker) | a demo to integrate Freemarker template engine. | +| [demo-template-thymeleaf](./demo-template-thymeleaf) | a demo to integrate Thymeleaf template engine. | +| [demo-template-beetl](./demo-template-beetl) | a demo to integrate Beetl template engine. | +| [demo-template-enjoy](./demo-template-enjoy) | a demo to integrate Enjoy template engine. | +| [demo-orm-jdbctemplate](./demo-orm-jdbctemplate) | a demo to integrate the Jdbc Template for operating database and easily encapsulate the generic Dao layer. | +| [demo-orm-jpa](./demo-orm-jpa) | a demo to integrate spring-boot-starter-data-jpa for operating database. | +| [demo-orm-mybatis](./demo-orm-mybatis) | a demo to integrate native mybatis by using [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter) dependency. | +| [demo-orm-mybatis-mapper-page](./demo-orm-mybatis-mapper-page) | a demo to integrate [Mapper](https://github.com/abel533/Mapper) and [PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) by using [mapper-spring-boot-starter](https://github.com/abel533/Mapper/tree/master/spring-boot-starter) and [pagehelper-spring-boot-starter](https://github.com/pagehelper/pagehelper-spring-boot) dependencies. | +| [demo-orm-mybatis-plus](./demo-orm-mybatis-plus) | a demo to integrate [mybatis-plus](https://mybatis.plus/en/) by using [mybatis-plus-boot-starter](http://mp.baomidou.com/) dependency, integrate BaseMapper / BaseService / ActiveRecord to operate database. | +| [demo-orm-beetlsql](./demo-orm-beetlsql) | a demo to integrate [beetl-sql](http://ibeetl.com/guide/#beetlsql) by using [beetl-framework-starter](http://ibeetl.com/guide/#beetlsql) dependency. | +| [demo-upload](./demo-upload) | a file upload demo, including local file upload and qiniu cloud file upload. | +| [demo-cache-redis](./demo-cache-redis) | a demo to integrate redis, operate data in redis, and use redis to cache data. | +| [demo-cache-ehcache](./demo-cache-ehcache) | a demo to integrate ehcache, and use ehcache to cache data. | +| [demo-email](./demo-email) | a demo to integrate email, including sending simple text email, HTML email (including template HTML email), attachment email, and static resource email. | +| [demo-task](./demo-task) | a demo to show easy to use scheduled task. | +| [demo-task-quartz](./demo-task-quartz) | a demo to integrate quartz for managing scheduled tasks, including adding new scheduled tasks, deleting scheduled tasks, suspending scheduled tasks, restoring scheduled tasks, modifying scheduled task startup times, and timing task list queries, and `providing front-end pages`. | +| [demo-task-xxl-job](./demo-task-xxl-job) | a demo to integrate [xxl-job](http://www.xuxueli.com/xxl-job/en/#/) for distributed scheduled tasks and provide methods to manage scheduled tasks bypass `xxl-job-admin`, including scheduled task lists, trigger lists, new scheduled tasks, deleted scheduled tasks, stopped scheduled tasks, and started scheduled tasks. Modify the scheduled task and manually trigger the scheduled task. | +| [demo-swagger](./demo-swagger) | a demo to integrate native `swagger` to manage and test API interfaces. | +| [demo-swagger-beauty](./demo-swagger-beauty) | a demo to integrate third part of swagger dependency [swagger-bootstrap-ui](https://github.com/xiaoymin/Swagger-Bootstrap-UI) to beautify document style and manage and test API interfaces. | +| [demo-rbac-security](./demo-rbac-security) | a demo to integrate spring security implement privilege management based on RBAC privilege model, supports custom filtering request, dynamic privilege authentication, uses JWT security authentication, supports online population statistics, manually kicks out users, etc. | +| [demo-rbac-shiro](./demo-rbac-shiro) | NOT FINISHED YET!
a demo to integrate shiro for authentication management. | +| [demo-session](./demo-session) | a demo to integrate Spring Session to implement Session sharing, restart program Session does not expire. | +| [demo-oauth](./demo-oauth) | NOT FINISHED YET!
a demo to implement the oauth server and to implement oauth2 protocol such as the authorization code, access token. | +| [demo-social](./demo-social) | a demo to integrate third-party login by using `justauth-spring-boot-starter` dependency to achieve QQ login, GitHub login, WeChat login, Google login, Microsoft login, Xiaomi login, enterprise WeChat login. | +| [demo-zookeeper](./demo-zookeeper) | a demo to integrate Zookeeper and AOP to implement distributed lock. | +| [demo-mq-rabbitmq](./demo-mq-rabbitmq) | a demo to integrate RabbitMQ implementation for message delivery and reception based on direct queue mode, fanout mode, topic mode, delay queue. | +| [demo-mq-rocketmq](./demo-mq-rocketmq) | NOT FINISHED YET!
a demo to integrate RocketMQ implementation for message delivery and reception. | +| [demo-mq-kafka](./demo-mq-kafka) | a demo to integrate Kafka implementation for message delivery and reception. | +| [demo-websocket](./demo-websocket) | a demo to integrate websocket, the backend actively pushes the server running status to front end. | +| [demo-websocket-socketio](./demo-websocket-socketio) | a demo to integrate websocket by using `netty-socketio`, implement a simple chat room. | +| [demo-ureport2](./demo-ureport2) | NOT FINISHED YET!
a demo to integrate [ureport2](https://github.com/youseries/ureport) to implement complex, customized Chinese-style reports. | +| [demo-uflo](./demo-uflo) | NOT FINISHED YET!
a demo to integrate [uflo](https://github.com/youseries/uflo)(process engine like Activiti and Flowable) to quickly implement a lightweight process engine. | +| [demo-urule](./demo-urule) | NOT FINISHED YET!
a demo to integrate [urule](https://github.com/youseries/urule)(rule engine like drools) fast implementation rule engine. | +| [demo-activiti](./demo-activiti) | NOT FINISHED YET!
a demo to integrate Activiti 7 process engine. | +| [demo-async](./demo-async) | asynchronous execution of tasks by using natively provided asynchronous task support. | +| [demo-war](./demo-war) | packaged into a war format configuration | +| [demo-elasticsearch](./demo-elasticsearch) | a demo to integrate ElasticSearch by using `spring-boot-starter-data-elasticsearch` to implement advanced techniques for using ElasticSearch, including creating indexes, configuring mappings, deleting indexes, adding and deleting basic operations, complex queries, advanced queries, aggregate queries, etc. | +| [demo-dubbo](./demo-dubbo) | a demo to integrate Dubbo, common module `spring-boot-demo-dubbo-common`, service provider `spring-boot-demo-dubbo-provider`, service consumer `spring-boot-demo-dubbo-consumer`. | +| [demo-mongodb](./demo-mongodb) | a demo to integrate MongoDB and use the official starter to CRUD. | +| [demo-neo4j](./demo-neo4j) | a demo to integrate Neo4j graph database to implement a campus character relationship network. | +| [demo-docker](./demo-docker) | docker container. | +| [demo-multi-datasource-jpa](./demo-multi-datasource-jpa) | a demo to implement JPA multi-datasource. | +| [demo-multi-datasource-mybatis](./demo-multi-datasource-mybatis) | a demo to implement Mybatis multi-datasource by using an open source solution from Mybatis-Plus. | +| [demo-sharding-jdbc](./demo-sharding-jdbc) | a demo to use `sharding-jdbc` to implement sub-database and sub-tables, while ORM uses Mybatis-Plus. | +| [demo-tio](./demo-tio) | NOT FINISHED YET!
a demo to integrate t-io(a network programming framework like netty). | +| demo-grpc | NOT FINISHED YET!
a demo to integrate Google grpc, need to be configure tls/ssl, see [ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5). | +| [demo-codegen](./demo-codegen) | a demo to integrate velocity template engine to implement code generator, improve development efficiency. | +| [demo-graylog](./demo-graylog) | a demo to integrate graylog for unified log collection. | +| demo-sso | NOT FINISHED YET!
a demo to integrate Single Sign On, see [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12). | +| [demo-ldap](./demo-ldap) | a demo to integrate LDAP to use `spring-boot-starter-data-ldap` to implement CURD operations and give the login demo, see [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23), thanks [@fxbin](https://github.com/fxbin). | +| [demo-dynamic-datasource](./demo-dynamic-datasource) | a demo to add datasource dynamically, switch datasource dynamically. | +| [demo-ratelimit-guava](./demo-ratelimit-guava) | a demo to use use Guava RateLimiter to protect API by standalone rate limiting. | +| [demo-ratelimit-redis](./demo-ratelimit-redis) | a demo to use Redis and Lua script implementation to protect API by distributed rate limiting. | +| [demo-https](./demo-https) | a demo to integrate HTTPS. | +| [demo-elasticsearch-rest-high-level-client](./demo-elasticsearch-rest-high-level-client) | a demo to integrate ElasticSearch 7.x version by using official Rest High Level Client to operate ES data. | +| [demo-flyway](./demo-flyway) | a demo to integrate Flyway to initialize tables and data in database, Flyway also support the sql script version control. | +| [demo-ureport2](./demo-ureport2) | a demo to integrate Ureport2 to design the Chinese complex report file. | + +### Thanks + +- jetbrains**Thanks JetBrains Offer Open Source Free License** +- [Thanks MyBatisCodeHelper-Pro(The Best Code Generator Plugin) Offer Permanent Activation Code](https://gejun123456.github.io/MyBatisCodeHelper-Pro/#/?id=mybatiscodehelper-pro) + +### License + +[MIT](http://opensource.org/licenses/MIT) + +Copyright (c) 2018 Yangkai.Shen diff --git a/README.md b/README.md index c6c493f..2d9b99a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ author JDK Spring Boot - LICENSE + LICENSE

@@ -20,18 +20,12 @@ ## 项目简介 -`spring boot demo` 是一个用来深度学习并实战 `spring boot` 的项目,目前总共包含 **`66`** 个集成demo,已经完成 **`54`** 个。 +`spring boot demo` 是一个用来深度学习并实战 `spring boot` 的项目,目前总共包含 **`66`** 个集成demo,已经完成 **`55`** 个。 -该项目已成功集成 actuator(`监控`)、admin(`可视化监控`)、logback(`日志`)、aopLog(`通过AOP记录web请求日志`)、统一异常处理(`json级别和页面级别`)、freemarker(`模板引擎`)、thymeleaf(`模板引擎`)、Beetl(`模板引擎`)、Enjoy(`模板引擎`)、JdbcTemplate(`通用JDBC操作数据库`)、JPA(`强大的ORM框架`)、mybatis(`强大的ORM框架`)、通用Mapper(`快速操作Mybatis`)、PageHelper(`通用的Mybatis分页插件`)、mybatis-plus(`快速操作Mybatis`)、BeetlSQL(`强大的ORM框架`)、upload(`本地文件上传和七牛云文件上传`)、redis(`缓存`)、ehcache(`缓存`)、email(`发送各种类型邮件`)、task(`基础定时任务`)、quartz(`动态管理定时任务`)、xxl-job(`分布式定时任务`)、swagger(`API接口管理测试`)、security(`基于RBAC的动态权限认证`)、SpringSession(`Session共享`)、Zookeeper(`结合AOP实现分布式锁`)、RabbitMQ(`消息队列`)、Kafka(`消息队列`)、websocket(`服务端推送监控服务器运行信息`)、socket.io(`聊天室`)、ureport2(`中国式报表`)、打包成`war`文件、集成 ElasticSearch(`基本操作和高级查询`)、Async(`异步任务`)、集成Dubbo(`采用官方的starter`)、MongoDB(`文档数据库`)、neo4j(`图数据库`)、docker(`容器化`)、`JPA多数据源`、`Mybatis多数据源`、`代码生成器`、GrayLog(`日志收集`)、JustAuth(`第三方登录`)、LDAP(`增删改查`)、`动态添加/切换数据源`、单机限流(`AOP + Guava RateLimiter`)、分布式限流(`AOP + Redis + Lua`)、ElasticSearch 7.x(`使用官方 Rest High Level Client`)、HTTPS、Flyway(`数据库初始化`)。 +该项目已成功集成 actuator(`监控`)、admin(`可视化监控`)、logback(`日志`)、aopLog(`通过AOP记录web请求日志`)、统一异常处理(`json级别和页面级别`)、freemarker(`模板引擎`)、thymeleaf(`模板引擎`)、Beetl(`模板引擎`)、Enjoy(`模板引擎`)、JdbcTemplate(`通用JDBC操作数据库`)、JPA(`强大的ORM框架`)、mybatis(`强大的ORM框架`)、通用Mapper(`快速操作Mybatis`)、PageHelper(`通用的Mybatis分页插件`)、mybatis-plus(`快速操作Mybatis`)、BeetlSQL(`强大的ORM框架`)、upload(`本地文件上传和七牛云文件上传`)、redis(`缓存`)、ehcache(`缓存`)、email(`发送各种类型邮件`)、task(`基础定时任务`)、quartz(`动态管理定时任务`)、xxl-job(`分布式定时任务`)、swagger(`API接口管理测试`)、security(`基于RBAC的动态权限认证`)、SpringSession(`Session共享`)、Zookeeper(`结合AOP实现分布式锁`)、RabbitMQ(`消息队列`)、Kafka(`消息队列`)、websocket(`服务端推送监控服务器运行信息`)、socket.io(`聊天室`)、ureport2(`中国式报表`)、打包成`war`文件、集成 ElasticSearch(`基本操作和高级查询`)、Async(`异步任务`)、集成Dubbo(`采用官方的starter`)、MongoDB(`文档数据库`)、neo4j(`图数据库`)、docker(`容器化`)、`JPA多数据源`、`Mybatis多数据源`、`代码生成器`、GrayLog(`日志收集`)、JustAuth(`第三方登录`)、LDAP(`增删改查`)、`动态添加/切换数据源`、单机限流(`AOP + Guava RateLimiter`)、分布式限流(`AOP + Redis + Lua`)、ElasticSearch 7.x(`使用官方 Rest High Level Client`)、HTTPS、Flyway(`数据库初始化`)、UReport2(`中国式复杂报表`)。 > 如果大家还有想要集成的demo,也可在 [issue](https://github.com/xkcoding/spring-boot-demo/issues/new) 里提需求。我会额外添加在 [TODO](./TODO.md) 列表里。✊ -## 感谢 - -- jetbrains**感谢 JetBrains 提供的免费开源 License** - -- [感谢史上最牛的代码生成插件 MyBatisCodeHelper-Pro 提供的永久激活码](https://gejun123456.github.io/MyBatisCodeHelper-Pro/#/?id=mybatiscodehelper-pro) - ## 分支介绍 - master 分支:基于 Spring Boot 版本 `2.1.0.RELEASE`,每个 Module 的 parent 依赖根目录下的 pom.xml,主要用于管理每个 Module 的通用依赖版本,方便大家学习。 @@ -56,322 +50,106 @@ 6. **`注意:每个 demo 均有详细的 README 配套,食用 demo 前记得先看看哦~`** 7. **`注意:运行各个 demo 之前,有些是需要事先初始化数据库数据的,亲们别忘记了哦~`** -## 开发计划 - -查看 [TODO](./TODO.md) 文件 - -## 各 Module 介绍 - -| Module 名称 | Module 介绍 | -| ------------------------------------------------------------ | ------------------------------------------------------------ | -| [spring-boot-demo-helloworld](./spring-boot-demo-helloworld) | spring-boot 的一个 helloworld | -| [spring-boot-demo-properties](./spring-boot-demo-properties) | spring-boot 读取配置文件中的内容 | -| [spring-boot-demo-actuator](./spring-boot-demo-actuator) | spring-boot 集成 spring-boot-starter-actuator 用于监控 spring-boot 的启动和运行状态 | -| [spring-boot-demo-admin-client](./spring-boot-demo-admin/spring-boot-demo-admin-client) | spring-boot 集成 spring-boot-admin 来可视化的监控 spring-boot 程序的运行状态,可以与 actuator 互相搭配使用,客户端示例 | -| [spring-boot-demo-admin-server](./spring-boot-demo-admin/spring-boot-demo-admin-server) | spring-boot 集成 spring-boot-admin 来可视化的监控 spring-boot 程序的运行状态,可以与 actuator 互相搭配使用,服务端示例 | -| [spring-boot-demo-logback](./spring-boot-demo-logback) | spring-boot 集成 logback 日志 | -| [spring-boot-demo-log-aop](./spring-boot-demo-log-aop) | spring-boot 使用 AOP 切面的方式记录 web 请求日志 | -| [spring-boot-demo-exception-handler](./spring-boot-demo-exception-handler) | spring-boot 统一异常处理,包括2种,第一种返回统一的 json 格式,第二种统一跳转到异常页面 | -| [spring-boot-demo-template-freemarker](./spring-boot-demo-template-freemarker) | spring-boot 集成 Freemarker 模板引擎 | -| [spring-boot-demo-template-thymeleaf](./spring-boot-demo-template-thymeleaf) | spring-boot 集成 Thymeleaf 模板引擎 | -| [spring-boot-demo-template-beetl](./spring-boot-demo-template-beetl) | spring-boot 集成 Beetl 模板引擎 | -| [spring-boot-demo-template-enjoy](./spring-boot-demo-template-enjoy) | spring-boot 集成 Enjoy 模板引擎 | -| [spring-boot-demo-orm-jdbctemplate](./spring-boot-demo-orm-jdbctemplate) | spring-boot 集成 Jdbc Template 操作数据库,并简易封装通用 Dao 层 | -| [spring-boot-demo-orm-jpa](./spring-boot-demo-orm-jpa) | spring-boot 集成 spring-boot-starter-data-jpa 操作数据库 | -| [spring-boot-demo-orm-mybatis](./spring-boot-demo-orm-mybatis) | spring-boot 集成原生mybatis,使用 [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter) 集成 | -| [spring-boot-demo-orm-mybatis-mapper-page](./spring-boot-demo-orm-mybatis-mapper-page) | spring-boot 集成[通用Mapper](https://github.com/abel533/Mapper)和[PageHelper](https://github.com/pagehelper/Mybatis-PageHelper),使用 [mapper-spring-boot-starter](https://github.com/abel533/Mapper/tree/master/spring-boot-starter) 和 [pagehelper-spring-boot-starter](https://github.com/pagehelper/pagehelper-spring-boot) 集成 | -| [spring-boot-demo-orm-mybatis-plus](./spring-boot-demo-orm-mybatis-plus) | spring-boot 集成 [mybatis-plus](https://mybatis.plus/),使用 [mybatis-plus-boot-starter](http://mp.baomidou.com/) 集成,集成 BaseMapper、BaseService、ActiveRecord 操作数据库 | -| [spring-boot-demo-orm-beetlsql](./spring-boot-demo-orm-beetlsql) | spring-boot 集成 [beetl-sql](http://ibeetl.com/guide/#beetlsql),使用 [beetl-framework-starter](http://ibeetl.com/guide/#beetlsql) 集成 | -| [spring-boot-demo-upload](./spring-boot-demo-upload) | spring-boot 文件上传示例,包含本地文件上传以及七牛云文件上传 | -| [spring-boot-demo-cache-redis](./spring-boot-demo-cache-redis) | spring-boot 整合 redis,操作redis中的数据,并使用redis缓存数据 | -| [spring-boot-demo-cache-ehcache](./spring-boot-demo-cache-ehcache) | spring-boot 整合 ehcache,使用 ehcache 缓存数据 | -| [spring-boot-demo-email](./spring-boot-demo-email) | spring-boot 整合 email,包括发送简单文本邮件、HTML邮件(包括模板HTML邮件)、附件邮件、静态资源邮件 | -| [spring-boot-demo-task](./spring-boot-demo-task) | spring-boot 快速实现定时任务 | -| [spring-boot-demo-task-quartz](./spring-boot-demo-task-quartz) | spring-boot 整合 quartz,并实现对定时任务的管理,包括新增定时任务,删除定时任务,暂停定时任务,恢复定时任务,修改定时任务启动时间,以及定时任务列表查询,`提供前端页面` | -| [spring-boot-demo-task-xxl-job](./spring-boot-demo-task-xxl-job) | spring-boot 整合[xxl-job](http://www.xuxueli.com/xxl-job/en/#/),并提供绕过 `xxl-job-admin` 对定时任务的管理的方法,包括定时任务列表,触发器列表,新增定时任务,删除定时任务,停止定时任务,启动定时任务,修改定时任务,手动触发定时任务 | -| [spring-boot-demo-swagger](./spring-boot-demo-swagger) | spring-boot 集成原生的 `swagger` 用于统一管理、测试 API 接口 | -| [spring-boot-demo-swagger-beauty](./spring-boot-demo-swagger-beauty) | spring-boot 集成第三方 `swagger` [swagger-bootstrap-ui](https://github.com/xiaoymin/Swagger-Bootstrap-UI) 美化API文档样式,用于统一管理、测试 API 接口 | -| [spring-boot-demo-rbac-security](./spring-boot-demo-rbac-security) | spring-boot 集成 spring security 完成基于RBAC权限模型的权限管理,支持自定义过滤请求,动态权限认证,使用 JWT 安全认证,支持在线人数统计,手动踢出用户等操作 | -| [spring-boot-demo-rbac-shiro](./spring-boot-demo-rbac-shiro) | spring-boot 集成 shiro 实现权限管理
待完成 | -| [spring-boot-demo-session](./spring-boot-demo-session) | spring-boot 集成 Spring Session 实现Session共享、重启程序Session不失效 | -| [spring-boot-demo-oauth](./spring-boot-demo-oauth) | spring-boot 实现 oauth 服务器功能,实现授权码机制
待完成 | -| [spring-boot-demo-social](./spring-boot-demo-social) | spring-boot 集成第三方登录,集成 `justauth-spring-boot-starter` 实现QQ登录、GitHub登录、微信登录、谷歌登录、微软登录、小米登录、企业微信登录。 | -| [spring-boot-demo-zookeeper](./spring-boot-demo-zookeeper) | spring-boot 集成 Zookeeper 结合AOP实现分布式锁 | -| [spring-boot-demo-mq-rabbitmq](./spring-boot-demo-mq-rabbitmq) | spring-boot 集成 RabbitMQ 实现基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收 | -| [spring-boot-demo-mq-rocketmq](./spring-boot-demo-mq-rocketmq) | spring-boot 集成 RocketMQ,实现消息的发送和接收
待完成 | -| [spring-boot-demo-mq-kafka](./spring-boot-demo-mq-kafka) | spring-boot 集成 kafka,实现消息的发送和接收 | -| [spring-boot-demo-websocket](./spring-boot-demo-websocket) | spring-boot 集成 websocket,后端主动推送前端服务器运行信息 | -| [spring-boot-demo-websocket-socketio](./spring-boot-demo-websocket-socketio) | spring-boot 使用 netty-socketio 集成 websocket,实现一个简单的聊天室 | -| [spring-boot-demo-ureport2](./spring-boot-demo-ureport2) | spring-boot 集成 ureport2 实现复杂的自定义的中国式报表
待完成 | -| [spring-boot-demo-uflo](./spring-boot-demo-uflo) | spring-boot 集成 uflo 快速实现轻量级流程引擎
待完成 | -| [spring-boot-demo-urule](./spring-boot-demo-urule) | spring-boot 集成 urule 快速实现规则引擎
待完成 | -| [spring-boot-demo-activiti](./spring-boot-demo-activiti) | spring-boot 集成 activiti 7 流程引擎
待完成 | -| [spring-boot-demo-async](./spring-boot-demo-async) | spring-boot 使用原生提供的异步任务支持,实现异步执行任务 | -| [spring-boot-demo-war](./spring-boot-demo-war) | spring-boot 打成 war 包的配置 | -| [spring-boot-demo-elasticsearch](./spring-boot-demo-elasticsearch) | spring-boot 集成 ElasticSearch,集成 `spring-boot-starter-data-elasticsearch` 完成对 ElasticSearch 的高级使用技巧,包括创建索引、配置映射、删除索引、增删改查基本操作、复杂查询、高级查询、聚合查询等 | -| [spring-boot-demo-dubbo](./spring-boot-demo-dubbo) | spring-boot 集成 Dubbo,分别为公共模块 `spring-boot-demo-dubbo-common`、服务提供方`spring-boot-demo-dubbo-provider`、服务调用方`spring-boot-demo-dubbo-consumer` | -| [spring-boot-demo-mongodb](./spring-boot-demo-mongodb) | spring-boot 集成 MongoDB,使用官方的 starter 实现增删改查 | -| [spring-boot-demo-neo4j](./spring-boot-demo-neo4j) | spring-boot 集成 Neo4j 图数据库,实现一个校园人物关系网的demo | -| [spring-boot-demo-docker](./spring-boot-demo-docker) | spring-boot 容器化 | -| [spring-boot-demo-multi-datasource-jpa](./spring-boot-demo-multi-datasource-jpa) | spring-boot 使用JPA集成多数据源 | -| [spring-boot-demo-multi-datasource-mybatis](./spring-boot-demo-multi-datasource-mybatis) | spring-boot 使用Mybatis集成多数据源,使用 Mybatis-Plus 提供的开源解决方案实现 | -| [spring-boot-demo-sharding-jdbc](./spring-boot-demo-sharding-jdbc) | spring-boot 使用 `sharding-jdbc` 实现分库分表,同时ORM采用 Mybatis-Plus | -| [spring-boot-demo-tio](./spring-boot-demo-tio) | spring-boot 集成 tio 网络编程框架
待完成 | -| [spring-boot-demo-grpc](./spring-boot-demo-grpc) | spring-boot 集成grpc,配置tls/ssl,参见[ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5)
待完成 | -| [spring-boot-demo-codegen](./spring-boot-demo-codegen) | spring-boot 集成 velocity 模板技术实现的代码生成器,简化开发 | -| [spring-boot-demo-graylog](./spring-boot-demo-graylog) | spring-boot 集成 graylog 实现日志统一收集 | -| spring-boot-demo-sso | spring-boot 集成 SSO 单点登录,参见 [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12)
待完成 | -| [spring-boot-demo-ldap](./spring-boot-demo-ldap) | spring-boot 集成 LDAP,集成 `spring-boot-starter-data-ldap` 完成对 Ldap 的基本 CURD操作, 并给出以登录为实战的 API 示例,参见 [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23),感谢 [@fxbin](https://github.com/fxbin) | -| [spring-boot-demo-dynamic-datasource](./spring-boot-demo-dynamic-datasource) | spring-boot 动态添加数据源、动态切换数据源 | -| [spring-boot-demo-ratelimit-guava](./spring-boot-demo-ratelimit-guava) | spring-boot 使用 Guava RateLimiter 实现单机版限流,保护 API | -| [spring-boot-demo-ratelimit-redis](./spring-boot-demo-ratelimit-redis) | spring-boot 使用 Redis + Lua 脚本实现分布式限流,保护 API | -| [spring-boot-demo-https](./spring-boot-demo-https) | spring-boot 集成 HTTPS | -| [spring-boot-demo-elasticsearch-rest-high-level-client](./spring-boot-demo-elasticsearch-rest-high-level-client) | spring boot 集成 ElasticSearch 7.x 版本,使用官方 Rest High Level Client 操作 ES 数据 | -| [spring-boot-demo-flyway](./spring-boot-demo-flyway) | spring boot 集成 Flyway,项目启动时初始化数据库表结构,同时支持数据库脚本版本控制 | - -## License - -[MIT](http://opensource.org/licenses/MIT) - -Copyright (c) 2018 Yangkai.Shen - ## 项目趋势 [![Stargazers over time](https://starchart.cc/xkcoding/spring-boot-demo.svg)](https://starchart.cc/xkcoding/spring-boot-demo) -## 附录 +## 其他 -### 根目录下的 pom.xml +### 团队纳新 -```xml - +组内招人啦,HC 巨多,Base 杭州,感兴趣的小伙伴,查看 [岗位详情](./jd.md) - - 4.0.0 +### 开源推荐 - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - spring-boot-demo-helloworld - spring-boot-demo-properties - spring-boot-demo-actuator - spring-boot-demo-admin - spring-boot-demo-logback - spring-boot-demo-log-aop - spring-boot-demo-exception-handler - spring-boot-demo-template-freemarker - spring-boot-demo-template-thymeleaf - spring-boot-demo-template-beetl - spring-boot-demo-template-enjoy - spring-boot-demo-orm-jdbctemplate - spring-boot-demo-orm-jpa - spring-boot-demo-orm-mybatis - spring-boot-demo-orm-mybatis-mapper-page - spring-boot-demo-orm-mybatis-plus - spring-boot-demo-orm-beetlsql - spring-boot-demo-upload - spring-boot-demo-cache-redis - spring-boot-demo-cache-ehcache - spring-boot-demo-email - spring-boot-demo-task - spring-boot-demo-task-quartz - spring-boot-demo-task-xxl-job - spring-boot-demo-swagger - spring-boot-demo-swagger-beauty - spring-boot-demo-rbac-security - spring-boot-demo-rbac-shiro - spring-boot-demo-session - spring-boot-demo-oauth - spring-boot-demo-social - spring-boot-demo-zookeeper - spring-boot-demo-mq-rabbitmq - spring-boot-demo-mq-rocketmq - spring-boot-demo-mq-kafka - spring-boot-demo-websocket - spring-boot-demo-websocket-socketio - spring-boot-demo-ureport2 - spring-boot-demo-uflo - spring-boot-demo-urule - spring-boot-demo-activiti - spring-boot-demo-async - spring-boot-demo-dubbo - spring-boot-demo-war - spring-boot-demo-elasticsearch - spring-boot-demo-mongodb - spring-boot-demo-neo4j - spring-boot-demo-docker - spring-boot-demo-multi-datasource-jpa - spring-boot-demo-multi-datasource-mybatis - spring-boot-demo-sharding-jdbc - spring-boot-demo-tio - spring-boot-demo-codegen - spring-boot-demo-graylog - spring-boot-demo-ldap - spring-boot-demo-dynamic-datasource - spring-boot-demo-ratelimit-guava - spring-boot-demo-ratelimit-redis - spring-boot-demo-elasticsearch-rest-high-level-client - spring-boot-demo-https - spring-boot-demo-flyway - - pom +- `JustAuth`:史上最全的整合第三方登录的开源库,https://github.com/justauth/JustAuth +- `Mica`:SpringBoot 微服务高效开发工具集,https://github.com/lets-mica/mica +- `awesome-collector`:https://github.com/P-P-X/awesome-collector +- `SpringBlade`:完整的线上解决方案(企业开发必备),https://github.com/chillzhuang/SpringBlade +- `Pig`:宇宙最强微服务认证授权脚手架(架构师必备),https://github.com/pigxcloud/pig - spring-boot-demo - http://xkcoding.com +### 开发计划 - - UTF-8 - UTF-8 - 1.8 - 1.8 - 1.8 - 2.1.0.RELEASE - 8.0.12 - 5.0.0 - 28.1-jre - 1.20 - +查看 [TODO](./TODO.md) 文件 - - - aliyun - aliyun - https://maven.aliyun.com/repository/public - - true - - - false - - - +### 各 Module 介绍 - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - mysql - mysql-connector-java - ${mysql.version} - - - - cn.hutool - hutool-all - ${hutool.version} - - - - com.google.guava - guava - ${guava.version} - - - - eu.bitwalker - UserAgentUtils - ${user.agent.version} - - - +| Module 名称 | Module 介绍 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [demo-helloworld](./demo-helloworld) | spring-boot 的一个 helloworld | +| [demo-properties](./demo-properties) | spring-boot 读取配置文件中的内容 | +| [demo-actuator](./demo-actuator) | spring-boot 集成 spring-boot-starter-actuator 用于监控 spring-boot 的启动和运行状态 | +| [demo-admin-client](./demo-admin/admin-client) | spring-boot 集成 spring-boot-admin 来可视化的监控 spring-boot 程序的运行状态,可以与 actuator 互相搭配使用,客户端示例 | +| [demo-admin-server](./demo-admin/admin-server) | spring-boot 集成 spring-boot-admin 来可视化的监控 spring-boot 程序的运行状态,可以与 actuator 互相搭配使用,服务端示例 | +| [demo-logback](./demo-logback) | spring-boot 集成 logback 日志 | +| [demo-log-aop](./demo-log-aop) | spring-boot 使用 AOP 切面的方式记录 web 请求日志 | +| [demo-exception-handler](./demo-exception-handler) | spring-boot 统一异常处理,包括2种,第一种返回统一的 json 格式,第二种统一跳转到异常页面 | +| [demo-template-freemarker](./demo-template-freemarker) | spring-boot 集成 Freemarker 模板引擎 | +| [demo-template-thymeleaf](./demo-template-thymeleaf) | spring-boot 集成 Thymeleaf 模板引擎 | +| [demo-template-beetl](./demo-template-beetl) | spring-boot 集成 Beetl 模板引擎 | +| [demo-template-enjoy](./demo-template-enjoy) | spring-boot 集成 Enjoy 模板引擎 | +| [demo-orm-jdbctemplate](./demo-orm-jdbctemplate) | spring-boot 集成 Jdbc Template 操作数据库,并简易封装通用 Dao 层 | +| [demo-orm-jpa](./demo-orm-jpa) | spring-boot 集成 spring-boot-starter-data-jpa 操作数据库 | +| [demo-orm-mybatis](./demo-orm-mybatis) | spring-boot 集成原生mybatis,使用 [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter) 集成 | +| [demo-orm-mybatis-mapper-page](./demo-orm-mybatis-mapper-page) | spring-boot 集成[通用Mapper](https://github.com/abel533/Mapper)和[PageHelper](https://github.com/pagehelper/Mybatis-PageHelper),使用 [mapper-spring-boot-starter](https://github.com/abel533/Mapper/tree/master/spring-boot-starter) 和 [pagehelper-spring-boot-starter](https://github.com/pagehelper/pagehelper-spring-boot) 集成 | +| [demo-orm-mybatis-plus](./demo-orm-mybatis-plus) | spring-boot 集成 [mybatis-plus](https://mybatis.plus/),使用 [mybatis-plus-boot-starter](http://mp.baomidou.com/) 集成,集成 BaseMapper、BaseService、ActiveRecord 操作数据库 | +| [demo-orm-beetlsql](./demo-orm-beetlsql) | spring-boot 集成 [beetl-sql](http://ibeetl.com/guide/#beetlsql),使用 [beetl-framework-starter](http://ibeetl.com/guide/#beetlsql) 集成 | +| [demo-upload](./demo-upload) | spring-boot 文件上传示例,包含本地文件上传以及七牛云文件上传 | +| [demo-cache-redis](./demo-cache-redis) | spring-boot 整合 redis,操作redis中的数据,并使用redis缓存数据 | +| [demo-cache-ehcache](./demo-cache-ehcache) | spring-boot 整合 ehcache,使用 ehcache 缓存数据 | +| [demo-email](./demo-email) | spring-boot 整合 email,包括发送简单文本邮件、HTML邮件(包括模板HTML邮件)、附件邮件、静态资源邮件 | +| [demo-task](./demo-task) | spring-boot 快速实现定时任务 | +| [demo-task-quartz](./demo-task-quartz) | spring-boot 整合 quartz,并实现对定时任务的管理,包括新增定时任务,删除定时任务,暂停定时任务,恢复定时任务,修改定时任务启动时间,以及定时任务列表查询,`提供前端页面` | +| [demo-task-xxl-job](./demo-task-xxl-job) | spring-boot 整合[xxl-job](http://www.xuxueli.com/xxl-job/en/#/),并提供绕过 `xxl-job-admin` 对定时任务的管理的方法,包括定时任务列表,触发器列表,新增定时任务,删除定时任务,停止定时任务,启动定时任务,修改定时任务,手动触发定时任务 | +| [demo-swagger](./demo-swagger) | spring-boot 集成原生的 `swagger` 用于统一管理、测试 API 接口 | +| [demo-swagger-beauty](./demo-swagger-beauty) | spring-boot 集成第三方 `swagger` [swagger-bootstrap-ui](https://github.com/xiaoymin/Swagger-Bootstrap-UI) 美化API文档样式,用于统一管理、测试 API 接口 | +| [demo-rbac-security](./demo-rbac-security) | spring-boot 集成 spring security 完成基于RBAC权限模型的权限管理,支持自定义过滤请求,动态权限认证,使用 JWT 安全认证,支持在线人数统计,手动踢出用户等操作 | +| [demo-rbac-shiro](./demo-rbac-shiro) | spring-boot 集成 shiro 实现权限管理
待完成 | +| [demo-session](./demo-session) | spring-boot 集成 Spring Session 实现Session共享、重启程序Session不失效 | +| [demo-oauth](./demo-oauth) | spring-boot 实现 oauth 服务器功能,实现授权码机制
待完成 | +| [demo-social](./demo-social) | spring-boot 集成第三方登录,集成 `justauth-spring-boot-starter` 实现QQ登录、GitHub登录、微信登录、谷歌登录、微软登录、小米登录、企业微信登录。 | +| [demo-zookeeper](./demo-zookeeper) | spring-boot 集成 Zookeeper 结合AOP实现分布式锁 | +| [demo-mq-rabbitmq](./demo-mq-rabbitmq) | spring-boot 集成 RabbitMQ 实现基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收 | +| [demo-mq-rocketmq](./demo-mq-rocketmq) | spring-boot 集成 RocketMQ,实现消息的发送和接收
待完成 | +| [demo-mq-kafka](./demo-mq-kafka) | spring-boot 集成 kafka,实现消息的发送和接收 | +| [demo-websocket](./demo-websocket) | spring-boot 集成 websocket,后端主动推送前端服务器运行信息 | +| [demo-websocket-socketio](./demo-websocket-socketio) | spring-boot 使用 netty-socketio 集成 websocket,实现一个简单的聊天室 | +| [demo-ureport2](./demo-ureport2) | spring-boot 集成 ureport2 实现复杂的自定义的中国式报表
待完成 | +| [demo-uflo](./demo-uflo) | spring-boot 集成 uflo 快速实现轻量级流程引擎
待完成 | +| [demo-urule](./demo-urule) | spring-boot 集成 urule 快速实现规则引擎
待完成 | +| [demo-activiti](./demo-activiti) | spring-boot 集成 activiti 7 流程引擎
待完成 | +| [demo-async](./demo-async) | spring-boot 使用原生提供的异步任务支持,实现异步执行任务 | +| [demo-war](./demo-war) | spring-boot 打成 war 包的配置 | +| [demo-elasticsearch](./demo-elasticsearch) | spring-boot 集成 ElasticSearch,集成 `spring-boot-starter-data-elasticsearch` 完成对 ElasticSearch 的高级使用技巧,包括创建索引、配置映射、删除索引、增删改查基本操作、复杂查询、高级查询、聚合查询等 | +| [demo-dubbo](./demo-dubbo) | spring-boot 集成 Dubbo,分别为公共模块 `spring-boot-demo-dubbo-common`、服务提供方`spring-boot-demo-dubbo-provider`、服务调用方`spring-boot-demo-dubbo-consumer` | +| [demo-mongodb](./demo-mongodb) | spring-boot 集成 MongoDB,使用官方的 starter 实现增删改查 | +| [demo-neo4j](./demo-neo4j) | spring-boot 集成 Neo4j 图数据库,实现一个校园人物关系网的demo | +| [demo-docker](./demo-docker) | spring-boot 容器化 | +| [demo-multi-datasource-jpa](./demo-multi-datasource-jpa) | spring-boot 使用JPA集成多数据源 | +| [demo-multi-datasource-mybatis](./demo-multi-datasource-mybatis) | spring-boot 使用Mybatis集成多数据源,使用 Mybatis-Plus 提供的开源解决方案实现 | +| [demo-sharding-jdbc](./demo-sharding-jdbc) | spring-boot 使用 `sharding-jdbc` 实现分库分表,同时ORM采用 Mybatis-Plus | +| [demo-tio](./demo-tio) | spring-boot 集成 tio 网络编程框架
待完成 | +| demo-grpc | spring-boot 集成grpc,配置tls/ssl,参见[ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5)
待完成 | +| [demo-codegen](./demo-codegen) | spring-boot 集成 velocity 模板技术实现的代码生成器,简化开发 | +| [demo-graylog](./demo-graylog) | spring-boot 集成 graylog 实现日志统一收集 | +| demo-sso | spring-boot 集成 SSO 单点登录,参见 [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12)
待完成 | +| [demo-ldap](./demo-ldap) | spring-boot 集成 LDAP,集成 `spring-boot-starter-data-ldap` 完成对 Ldap 的基本 CURD操作, 并给出以登录为实战的 API 示例,参见 [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23),感谢 [@fxbin](https://github.com/fxbin) | +| [demo-dynamic-datasource](./demo-dynamic-datasource) | spring-boot 动态添加数据源、动态切换数据源 | +| [demo-ratelimit-guava](./demo-ratelimit-guava) | spring-boot 使用 Guava RateLimiter 实现单机版限流,保护 API | +| [demo-ratelimit-redis](./demo-ratelimit-redis) | spring-boot 使用 Redis + Lua 脚本实现分布式限流,保护 API | +| [demo-https](./demo-https) | spring-boot 集成 HTTPS | +| [demo-elasticsearch-rest-high-level-client](./demo-elasticsearch-rest-high-level-client) | spring boot 集成 ElasticSearch 7.x 版本,使用官方 Rest High Level Client 操作 ES 数据 | +| [demo-flyway](./demo-flyway) | spring boot 集成 Flyway,项目启动时初始化数据库表结构,同时支持数据库脚本版本控制 | +| [demo-ureport2](./demo-ureport2) | spring boot 集成 Ureport2,实现中国式复杂报表设计 | + +### 感谢 - - - - - maven-clean-plugin - 3.0.0 - - - maven-resources-plugin - 3.0.2 - - - maven-compiler-plugin - 3.7.0 - - - maven-surefire-plugin - 2.20.1 - - - maven-jar-plugin - 3.0.2 - - - maven-install-plugin - 2.5.2 - - - maven-deploy-plugin - 2.8.2 - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - - -
-``` +- jetbrains**感谢 JetBrains 提供的免费开源 License** -### 官方提供的 starter 介绍 +- [感谢史上最牛的代码生成插件 MyBatisCodeHelper-Pro 提供的永久激活码](https://gejun123456.github.io/MyBatisCodeHelper-Pro/#/?id=mybatiscodehelper-pro) -| 名称 | 描述 | -| :------------------------------------- | :---------------------------------------------------------- | -| spring-boot-starter | Spring Boot 核心包,包括自动装配,日志,以及YAML文件解析 | -| spring-boot-starter-actuator | 帮助在生产环境下监控和管理 Spring Boot 应用 | -| spring-boot-starter-amqp | Spring Boot 快速集成 RabbitMQ | -| spring-boot-starter-aop | 提供切面编程特性,包含 spring-aop 和 AspectJ 依赖 | -| spring-boot-starter-batch | 快速集成 Spring Batch 批处理框架,包括操作 HSQLDB 数据库 | -| spring-boot-starter-cache | Support for Spring’s Cache abstraction. | -| spring-boot-starter-data-elasticsearch | Spring Boot 快速集成 ElasticSearch 查询分析引擎 | -| spring-boot-starter-data-jpa | Spring Boot 快速集成 JPA 操作数据库 | -| spring-boot-starter-data-mongodb | Spring Boot 快速集成 MongoDB 非关系型数据库 | -| spring-boot-starter-data-rest | Spring Boot 暴露数据库查询端点为 REST 服务 | -| spring-boot-starter-data-solr | Spring Boot 快速集成 Solr 实现全文索引 | -| spring-boot-starter-freemarker | 提供 FreeMarker 模板引擎 | -| spring-boot-starter-groovy-templates | 提供 Groovy 模板引擎 | -| spring-boot-starter-integration | 提供通用的集成 spring-integration 模块 | -| spring-boot-starter-jdbc | 快速集成 JDBC 操作数据库 | -| spring-boot-starter-jersey | 提供 Jersey 提供 RESTful 服务 | -| spring-boot-starter-jta-atomikos | 集成 JTA Atomikos 实现分布式事务 | -| spring-boot-starter-jta-bitronix | 集成 JTA Bitronix 实现分布式事务 | -| spring-boot-starter-mail | 快速邮件集成 | -| spring-boot-starter-mustache | 提供 Mustache 模板引擎 | -| spring-boot-starter-redis | Spring Boot 快速集成 Redis | -| spring-boot-starter-security | Support for spring-security. | -| spring-boot-starter-social-facebook | Support for spring-social-facebook. | -| spring-boot-starter-social-linkedin | Support for spring-social-linkedin. | -| spring-boot-starter-social-twitter | Support for spring-social-twitter. | -| spring-boot-starter-test | 提供通用单元测试依赖,包括 JUnit, Hamcrest , Mockito | -| spring-boot-starter-thymeleaf | 提供 Thymeleaf 模板引擎,包括 Thymeleaf 的自动装配等 | -| spring-boot-starter-velocity | 提供 Velocity 模板引擎 | -| spring-boot-starter-web | 提供全栈的 web 开发特性,包括 Spring MVC 依赖和 Tomcat 容器 | -| spring-boot-starter-websocket | Spring Boot 集成 WebSocket 功能 | -| spring-boot-starter-ws | Spring Boot 集成 WebService 功能 | +### License -### 开源推荐 +[MIT](http://opensource.org/licenses/MIT) -- `JustAuth`:史上最全的整合第三方登录的开源库,https://github.com/justauth/JustAuth -- `Mica`:SpringBoot 微服务高效开发工具集,https://github.com/lets-mica/mica -- `awesome-collector`:https://github.com/P-P-X/awesome-collector -- `SpringBlade`:完整的线上解决方案(企业开发必备),https://github.com/chillzhuang/SpringBlade -- `Pig`:宇宙最强微服务认证授权脚手架(架构师必备),https://github.com/pigxcloud/pig +Copyright (c) 2018 Yangkai.Shen \ No newline at end of file diff --git a/TODO.en.md b/TODO.en.md index bc93416..ae1bca2 100644 --- a/TODO.en.md +++ b/TODO.en.md @@ -1,73 +1,73 @@ # spring-boot-demo Project TODO List -## Module plan (completed: 54 / 66) +## Module plan (completed: 55 / 66) -- [x] ~~spring-boot-demo-helloworld(helloworld example)~~ -- [x] ~~spring-boot-demo-properties (read configuration file information)~~ -- [x] ~~spring-boot-demo-actuator (endpoint monitoring for Spring boot)~~ -- [x] ~~spring-boot-demo-admin-client (for Spring boot visual control client)~~ -- [x] ~~spring-boot-demo-admin-server (for Spring boot visual control server)~~ -- [x] ~~spring-boot-demo-logback (integrated logback log)~~ -- [x] ~~spring-boot-demo-log-aop (use AOP to intercept request log information)~~ -- [x] ~~spring-boot-demo-exception-handler (unified exception handling)~~ -- [x] ~~spring-boot-demo-template-freemarker (using template engine - Freemarker)~~ -- [x] ~~spring-boot-demo-template-thymeleaf (using template engine - thymeleaf)~~ -- [x] ~~spring-boot-demo-template-beetl (using template engine - beetl)~~ -- [x] ~~spring-boot-demo-template-enjoy (using template engine - JFinal-Enjoy)~~ -- [x] ~~spring-boot-demo-upload (upload - integrated local upload and seven cattle cloud upload)~~ -- [x] ~~spring-boot-demo-orm-jdbctemplate (operating SQL relational database - JdbcTemplate)~~ -- [x] ~~spring-boot-demo-orm-jpa (operating SQL Relational Database - JPA)~~ -- [x] ~~spring-boot-demo-orm-mybatis (operating SQL relational database - mybatis)~~ -- [x] ~~spring-boot-demo-orm-mybatis-mapper-page (operating SQL relational database - integrating mybatis generic Mapper, PageHelper)~~ -- [x] ~~spring-boot-demo-orm-mybatis-plus (operating SQL relational database - integrating mybatis-plus, Mapper, ActiveRecord)~~ -- [x] ~~spring-boot-demo-orm-beetlsql (operating SQL relational database - beetlSQL)~~ -- [x] ~~spring-boot-demo-cache-redis (using redis for caching)~~ -- [x] ~~spring-boot-demo-cache-ehcache (using Ehcache for caching)~~ -- [x] ~~spring-boot-demo-email (integrated mail service)~~ -- [x] ~~spring-boot-demo-task (scheduled task - Task implementation)~~ -- [x] ~~spring-boot-demo-task-quartz (scheduled task - Quartz implementation)~~ -- [x] ~~spring-boot-demo-task-xxl-job (scheduled task - XXL-JOB for Distributed Scheduling)~~ -- [x] ~~spring-boot-demo-swagger (integrated Swagger for API interface test management)~~ -- [x] ~~spring-boot-demo-swagger-beauty (integrated custom and more beautiful Swagger test management of API interface)~~ -- [x] ~~spring-boot-demo-rbac-security (implementing RBAC-based permission model - Spring Security)~~ -- [ ] spring-boot-demo-rbac-shiro (implementing RBAC-based permission model - shiro) -- [x] ~~spring-boot-demo-session(unified Session Management)~~ -- [ ] spring-boot-demo-oauth (OAuth2 certification) -- [x] ~~spring-boot-demo-social (integrated JustAuth implements third-party authorization verification, and implements third-party logins such as QQ, WeChat, GitHub, Google, Xiaomi, etc.)~~ -- [x] ~~spring-boot-demo-zookeeper (use zookeeper to implement distributed locks with AOP)~~ -- [x] ~~spring-boot-demo-mq-rabbitmq (integrated messaging middleware - RabbitMQ)~~ -- [ ] spring-boot-demo-mq-rocketmq (integrated messaging middleware - RocketMQ) -- [x] ~~spring-boot-demo-mq-kafka (integrated message middleware - Kafka)~~ -- [x] ~~spring-boot-demo-websocket (integrated websocket service)~~ -- [x] ~~spring-boot-demo-websocket-socketio (integrated socketio implements websocket service)~~ -- [ ] spring-boot-demo-ureport2 (integrated ureport2 implements a custom complex Chinese-style reporting engine) -- [ ] spring-boot-demo-uflo (integrated uflo implementation process control engine) -- [ ] spring-boot-demo-urule (integrated urule implementation rules engine) -- [ ] spring-boot-demo-activiti (integrated of Activiti to implement process control engine) -- [x] ~~spring-boot-demo-async (Spring boot implements asynchronous calls)~~ -- [x] ~~spring-boot-demo-dubbo (integrated dubbo)~~ -- [x] ~~spring-boot-demo-war (packaged into a war package)~~ -- [x] ~~spring-boot-demo-elasticsearch (integrated ElasticSearch)~~ -- [x] ~~spring-boot-demo-mongodb (integrated MongoDb)~~ -- [x] ~~spring-boot-demo-neo4j (integrated neo4j graph database)~~ -- [x] ~~spring-boot-demo-docker (packaged into docker image)~~ -- [x] ~~spring-boot-demo-multi-datasource-jpa (integrated JPA multi data source)~~ -- [x] ~~spring-boot-demo-multi-datasource-mybatis (integrated with mybatis multi-data source)~~ -- [x] ~~spring-boot-demo-sharding-jdbc (integrated sharding-jdbc implementation sub-library table)~~ -- [ ] spring-boot-demo-tio (integrated t-io) -- [ ] spring-boot-demo-grpc (integrated grpc, configure tls/ssl) see [ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5) -- [x] ~~spring-boot-demo-codegen (integrated velocity auto-generated code)~~ -- [x] ~~spring-boot-demo-graylog (integrated gralog log management)~~ -- [ ] spring-boot-demo-sso (integrated single sign on) see [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12) -- [x] ~~spring-boot-demo-ldap (integrated ldap)see [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23)~~ -- [x] ~~spring-boot-demo-dynamic-datasource(add datasource dynamically, switch datasource dynamically)~~ -- [x] ~~spring-boot-demo-ratelimit-guava(use Guava RateLimiter to protect API by standalone rate limiting)~~ -- [x] ~~spring-boot-demo-ratelimit-redis(use Redis and Lua script implementation to protect API by distributed rate limiting)~~ -- [x] ~~spring-boot-demo-https(integrated HTTPS)~~ -- [x] ~~spring-boot-demo-elasticsearch-rest-high-level-client(integrated Elasticsearch 7.x version,use official Rest High Level Client to operate ES data)~~ -- [ ] spring-boot-demo-springbatch(data process) -- [ ] spring-boot-demo-security-justauth(use JustAuth to login GitHub,and use Spring-Security to manage login state) -- [x] ~~spring-boot-demo-flyway(integrated Flyway to initialize tables and data in database, Flyway also support the sql script version control)~~ +- [x] ~~demo-helloworld(helloworld example)~~ +- [x] ~~demo-properties (read configuration file information)~~ +- [x] ~~demo-actuator (endpoint monitoring for Spring boot)~~ +- [x] ~~demo-admin-client (for Spring boot visual control client)~~ +- [x] ~~demo-admin-server (for Spring boot visual control server)~~ +- [x] ~~demo-logback (integrated logback log)~~ +- [x] ~~demo-log-aop (use AOP to intercept request log information)~~ +- [x] ~~demo-exception-handler (unified exception handling)~~ +- [x] ~~demo-template-freemarker (using template engine - Freemarker)~~ +- [x] ~~demo-template-thymeleaf (using template engine - thymeleaf)~~ +- [x] ~~demo-template-beetl (using template engine - beetl)~~ +- [x] ~~demo-template-enjoy (using template engine - JFinal-Enjoy)~~ +- [x] ~~demo-upload (upload - integrated local upload and seven cattle cloud upload)~~ +- [x] ~~demo-orm-jdbctemplate (operating SQL relational database - JdbcTemplate)~~ +- [x] ~~demo-orm-jpa (operating SQL Relational Database - JPA)~~ +- [x] ~~demo-orm-mybatis (operating SQL relational database - mybatis)~~ +- [x] ~~demo-orm-mybatis-mapper-page (operating SQL relational database - integrating mybatis generic Mapper, PageHelper)~~ +- [x] ~~demo-orm-mybatis-plus (operating SQL relational database - integrating mybatis-plus, Mapper, ActiveRecord)~~ +- [x] ~~demo-orm-beetlsql (operating SQL relational database - beetlSQL)~~ +- [x] ~~demo-cache-redis (using redis for caching)~~ +- [x] ~~demo-cache-ehcache (using Ehcache for caching)~~ +- [x] ~~demo-email (integrated mail service)~~ +- [x] ~~demo-task (scheduled task - Task implementation)~~ +- [x] ~~demo-task-quartz (scheduled task - Quartz implementation)~~ +- [x] ~~demo-task-xxl-job (scheduled task - XXL-JOB for Distributed Scheduling)~~ +- [x] ~~demo-swagger (integrated Swagger for API interface test management)~~ +- [x] ~~demo-swagger-beauty (integrated custom and more beautiful Swagger test management of API interface)~~ +- [x] ~~demo-rbac-security (implementing RBAC-based permission model - Spring Security)~~ +- [ ] demo-rbac-shiro (implementing RBAC-based permission model - shiro) +- [x] ~~demo-session(unified Session Management)~~ +- [ ] demo-oauth (OAuth2 certification) +- [x] ~~demo-social (integrated JustAuth implements third-party authorization verification, and implements third-party logins such as QQ, WeChat, GitHub, Google, Xiaomi, etc.)~~ +- [x] ~~demo-zookeeper (use zookeeper to implement distributed locks with AOP)~~ +- [x] ~~demo-mq-rabbitmq (integrated messaging middleware - RabbitMQ)~~ +- [ ] demo-mq-rocketmq (integrated messaging middleware - RocketMQ) +- [x] ~~demo-mq-kafka (integrated message middleware - Kafka)~~ +- [x] ~~demo-websocket (integrated websocket service)~~ +- [x] ~~demo-websocket-socketio (integrated socketio implements websocket service)~~ +- [x] ~~demo-ureport2 (integrated ureport2 implements a custom complex Chinese-style reporting engine)~~ +- [ ] demo-uflo (integrated uflo implementation process control engine) +- [ ] demo-urule (integrated urule implementation rules engine) +- [ ] demo-activiti (integrated of Activiti to implement process control engine) +- [x] ~~demo-async (Spring boot implements asynchronous calls)~~ +- [x] ~~demo-dubbo (integrated dubbo)~~ +- [x] ~~demo-war (packaged into a war package)~~ +- [x] ~~demo-elasticsearch (integrated ElasticSearch)~~ +- [x] ~~demo-mongodb (integrated MongoDb)~~ +- [x] ~~demo-neo4j (integrated neo4j graph database)~~ +- [x] ~~demo-docker (packaged into docker image)~~ +- [x] ~~demo-multi-datasource-jpa (integrated JPA multi data source)~~ +- [x] ~~demo-multi-datasource-mybatis (integrated with mybatis multi-data source)~~ +- [x] ~~demo-sharding-jdbc (integrated sharding-jdbc implementation sub-library table)~~ +- [ ] demo-tio (integrated t-io) +- [ ] demo-grpc (integrated grpc, configure tls/ssl) see [ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5) +- [x] ~~demo-codegen (integrated velocity auto-generated code)~~ +- [x] ~~demo-graylog (integrated gralog log management)~~ +- [ ] demo-sso (integrated single sign on) see [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12) +- [x] ~~demo-ldap (integrated ldap)see [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23)~~ +- [x] ~~demo-dynamic-datasource(add datasource dynamically, switch datasource dynamically)~~ +- [x] ~~demo-ratelimit-guava(use Guava RateLimiter to protect API by standalone rate limiting)~~ +- [x] ~~demo-ratelimit-redis(use Redis and Lua script implementation to protect API by distributed rate limiting)~~ +- [x] ~~demo-https(integrated HTTPS)~~ +- [x] ~~demo-elasticsearch-rest-high-level-client(integrated Elasticsearch 7.x version,use official Rest High Level Client to operate ES data)~~ +- [ ] demo-springbatch(data process) +- [ ] demo-security-justauth(use JustAuth to login GitHub,and use Spring-Security to manage login state) +- [x] ~~demo-flyway(integrated Flyway to initialize tables and data in database, Flyway also support the sql script version control)~~ ## Remarks diff --git a/TODO.md b/TODO.md index 9f90735..0b35d26 100644 --- a/TODO.md +++ b/TODO.md @@ -1,73 +1,73 @@ # spring-boot-demo 项目待办列表 -## 模块计划(已完成:54 / 66) +## 模块计划(已完成:55 / 66) -- [x] ~~spring-boot-demo-helloworld(Helloworld 示例)~~ -- [x] ~~spring-boot-demo-properties(读取配置文件信息)~~ -- [x] ~~spring-boot-demo-actuator(对 Spring boot 的端点监控)~~ -- [x] ~~spring-boot-demo-admin-client(对 Spring boot 可视化管控 客户端)~~ -- [x] ~~spring-boot-demo-admin-server(对 Spring boot 可视化管控 服务端)~~ -- [x] ~~spring-boot-demo-logback(集成 logback 日志)~~ -- [x] ~~spring-boot-demo-log-aop(使用 AOP 拦截请求日志信息)~~ -- [x] ~~spring-boot-demo-exception-handler(统一异常处理)~~ -- [x] ~~spring-boot-demo-template-freemarker(使用模板引擎 - Freemarker)~~ -- [x] ~~spring-boot-demo-template-thymeleaf(使用模板引擎 - thymeleaf)~~ -- [x] ~~spring-boot-demo-template-beetl(使用模板引擎 - beetl)~~ -- [x] ~~spring-boot-demo-template-enjoy(使用模板引擎 - JFinal-Enjoy)~~ -- [x] ~~spring-boot-demo-upload(上传 - 集成本地上传和七牛云上传)~~ -- [x] ~~spring-boot-demo-orm-jdbctemplate(操作 SQL 关系型数据库 - JdbcTemplate)~~ -- [x] ~~spring-boot-demo-orm-jpa(操作 SQL 关系型数据库 - JPA)~~ -- [x] ~~spring-boot-demo-orm-mybatis(操作 SQL 关系型数据库 - mybatis)~~ -- [x] ~~spring-boot-demo-orm-mybatis-mapper-page(操作 SQL 关系型数据库 - 集成mybatis通用Mapper,PageHelper)~~ -- [x] ~~spring-boot-demo-orm-mybatis-plus(操作 SQL 关系型数据库 - 集成mybatis-plus,Mapper操作、ActiveRecord操作)~~ -- [x] ~~spring-boot-demo-orm-beetlsql(操作 SQL 关系型数据库 - beetlSQL)~~ -- [x] ~~spring-boot-demo-cache-redis(使用 redis 进行缓存)~~ -- [x] ~~spring-boot-demo-cache-ehcache(使用 Ehcache 进行缓存)~~ -- [x] ~~spring-boot-demo-email(集成邮件服务)~~ -- [x] ~~spring-boot-demo-task(定时任务 - Task 实现)~~ -- [x] ~~spring-boot-demo-task-quartz(定时任务 - Quartz 实现)~~ -- [x] ~~spring-boot-demo-task-xxl-job(定时任务 - XXL-JOB 实现分布式调度)~~ -- [x] ~~spring-boot-demo-swagger(集成 Swagger 对 API 接口进行测试管理)~~ -- [x] ~~spring-boot-demo-swagger-beauty(集成自定义且更加美观的 Swagger 对 API 接口进行测试管理)~~ -- [x] ~~spring-boot-demo-rbac-security(实现基于 RBAC 的权限模型 - Spring Security)~~ -- [ ] spring-boot-demo-rbac-shiro(实现基于 RBAC 的权限模型 - shiro) -- [x] ~~spring-boot-demo-session(统一 Session 管理)~~ -- [ ] spring-boot-demo-oauth(OAuth2 认证) -- [x] ~~spring-boot-demo-social(集成 JustAuth 实现第三方授权验证,实现 QQ、微信、GitHub、谷歌、小米等第三方登录)~~ -- [x] ~~spring-boot-demo-zookeeper(使用 zookeeper 结合AOP实现分布式锁)~~ -- [x] ~~spring-boot-demo-mq-rabbitmq(集成消息中间件 - RabbitMQ)~~ -- [ ] spring-boot-demo-mq-rocketmq(集成消息中间件 - RocketMQ) -- [x] ~~spring-boot-demo-mq-kafka(集成消息中间件 - Kafka)~~ -- [x] ~~spring-boot-demo-websocket(集成 websocket 服务)~~ -- [x] ~~spring-boot-demo-websocket-socketio(集成 socketio 实现 websocket 服务)~~ -- [ ] spring-boot-demo-ureport2 (集成 ureport2 实现自定义的复杂中国式报表引擎) -- [ ] spring-boot-demo-uflo(集成 uflo 实现流程控制引擎) -- [ ] spring-boot-demo-urule(集成 urule 实现规则引擎) -- [ ] spring-boot-demo-activiti(集成 Activiti 实现流程控制引擎) -- [x] ~~spring-boot-demo-async(Spring boot 实现异步调用)~~ -- [x] ~~spring-boot-demo-dubbo(集成 dubbo)~~ -- [x] ~~spring-boot-demo-war(打包成war包)~~ -- [x] ~~spring-boot-demo-elasticsearch(集成 ElasticSearch)~~ -- [x] ~~spring-boot-demo-mongodb(集成 MongoDb)~~ -- [x] ~~spring-boot-demo-neo4j(集成 neo4j 图数据库)~~ -- [x] ~~spring-boot-demo-docker(打包成 docker 镜像)~~ -- [x] ~~spring-boot-demo-multi-datasource-jpa(集成JPA多数据源)~~ -- [x] ~~spring-boot-demo-multi-datasource-mybatis(集成mybatis多数据源)~~ -- [x] ~~spring-boot-demo-sharding-jdbc(集成 sharding-jdbc 实现分库分表)~~ -- [ ] spring-boot-demo-tio(集成 tio) -- [ ] spring-boot-demo-grpc(集成grpc,配置tls/ssl)参见[ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5) -- [x] ~~spring-boot-demo-codegen(集成 velocity 自动生成代码)~~ -- [x] ~~spring-boot-demo-graylog(集成 gralog 日志管理)~~ -- [ ] spring-boot-demo-sso(集成单点登录)参见 [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12) -- [x] ~~spring-boot-demo-ldap (集成 ldap)参见 [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23)~~ -- [x] ~~spring-boot-demo-dynamic-datasource(动态添加数据源,切换数据源)~~ -- [x] ~~spring-boot-demo-ratelimit-guava(单机限流保护API,集成 Guava 的 RateLimiter)~~ -- [x] ~~spring-boot-demo-ratelimit-redis(分布式限流保护API,使用 Redis + lua 脚本实现)~~ -- [x] ~~spring-boot-demo-https(集成 HTTPS)~~ -- [x] ~~spring-boot-demo-elasticsearch-rest-high-level-client(集成 Elasticsearch 7.x 版本,使用官方 rest high level client操作 ES 数据)~~ -- [ ] spring-boot-demo-springbatch(数据处理) -- [ ] spring-boot-demo-security-justauth(使用 JustAuth 登录 GitHub,使用 Security 管理登录状态) -- [x] ~~spring-boot-demo-flyway(集成 Flyway,项目启动时初始化数据库表结构,同时支持数据库脚本版本控制)~~ +- [x] ~~demo-helloworld(Helloworld 示例)~~ +- [x] ~~demo-properties(读取配置文件信息)~~ +- [x] ~~demo-actuator(对 Spring boot 的端点监控)~~ +- [x] ~~demo-admin-client(对 Spring boot 可视化管控 客户端)~~ +- [x] ~~demo-admin-server(对 Spring boot 可视化管控 服务端)~~ +- [x] ~~demo-logback(集成 logback 日志)~~ +- [x] ~~demo-log-aop(使用 AOP 拦截请求日志信息)~~ +- [x] ~~demo-exception-handler(统一异常处理)~~ +- [x] ~~demo-template-freemarker(使用模板引擎 - Freemarker)~~ +- [x] ~~demo-template-thymeleaf(使用模板引擎 - thymeleaf)~~ +- [x] ~~demo-template-beetl(使用模板引擎 - beetl)~~ +- [x] ~~demo-template-enjoy(使用模板引擎 - JFinal-Enjoy)~~ +- [x] ~~demo-upload(上传 - 集成本地上传和七牛云上传)~~ +- [x] ~~demo-orm-jdbctemplate(操作 SQL 关系型数据库 - JdbcTemplate)~~ +- [x] ~~demo-orm-jpa(操作 SQL 关系型数据库 - JPA)~~ +- [x] ~~demo-orm-mybatis(操作 SQL 关系型数据库 - mybatis)~~ +- [x] ~~demo-orm-mybatis-mapper-page(操作 SQL 关系型数据库 - 集成mybatis通用Mapper,PageHelper)~~ +- [x] ~~demo-orm-mybatis-plus(操作 SQL 关系型数据库 - 集成mybatis-plus,Mapper操作、ActiveRecord操作)~~ +- [x] ~~demo-orm-beetlsql(操作 SQL 关系型数据库 - beetlSQL)~~ +- [x] ~~demo-cache-redis(使用 redis 进行缓存)~~ +- [x] ~~demo-cache-ehcache(使用 Ehcache 进行缓存)~~ +- [x] ~~demo-email(集成邮件服务)~~ +- [x] ~~demo-task(定时任务 - Task 实现)~~ +- [x] ~~demo-task-quartz(定时任务 - Quartz 实现)~~ +- [x] ~~demo-task-xxl-job(定时任务 - XXL-JOB 实现分布式调度)~~ +- [x] ~~demo-swagger(集成 Swagger 对 API 接口进行测试管理)~~ +- [x] ~~demo-swagger-beauty(集成自定义且更加美观的 Swagger 对 API 接口进行测试管理)~~ +- [x] ~~demo-rbac-security(实现基于 RBAC 的权限模型 - Spring Security)~~ +- [ ] demo-rbac-shiro(实现基于 RBAC 的权限模型 - shiro) +- [x] ~~demo-session(统一 Session 管理)~~ +- [ ] demo-oauth(OAuth2 认证) +- [x] ~~demo-social(集成 JustAuth 实现第三方授权验证,实现 QQ、微信、GitHub、谷歌、小米等第三方登录)~~ +- [x] ~~demo-zookeeper(使用 zookeeper 结合AOP实现分布式锁)~~ +- [x] ~~demo-mq-rabbitmq(集成消息中间件 - RabbitMQ)~~ +- [ ] demo-mq-rocketmq(集成消息中间件 - RocketMQ) +- [x] ~~demo-mq-kafka(集成消息中间件 - Kafka)~~ +- [x] ~~demo-websocket(集成 websocket 服务)~~ +- [x] ~~demo-websocket-socketio(集成 socketio 实现 websocket 服务)~~ +- [x] ~~demo-ureport2 (集成 ureport2 实现自定义的复杂中国式报表引擎)~~ +- [ ] demo-uflo(集成 uflo 实现流程控制引擎) +- [ ] demo-urule(集成 urule 实现规则引擎) +- [ ] demo-activiti(集成 Activiti 实现流程控制引擎) +- [x] ~~demo-async(Spring boot 实现异步调用)~~ +- [x] ~~demo-dubbo(集成 dubbo)~~ +- [x] ~~demo-war(打包成war包)~~ +- [x] ~~demo-elasticsearch(集成 ElasticSearch)~~ +- [x] ~~demo-mongodb(集成 MongoDb)~~ +- [x] ~~demo-neo4j(集成 neo4j 图数据库)~~ +- [x] ~~demo-docker(打包成 docker 镜像)~~ +- [x] ~~demo-multi-datasource-jpa(集成JPA多数据源)~~ +- [x] ~~demo-multi-datasource-mybatis(集成mybatis多数据源)~~ +- [x] ~~demo-sharding-jdbc(集成 sharding-jdbc 实现分库分表)~~ +- [ ] demo-tio(集成 tio) +- [ ] demo-grpc(集成grpc,配置tls/ssl)参见[ISSUE#5](https://github.com/xkcoding/spring-boot-demo/issues/5) +- [x] ~~demo-codegen(集成 velocity 自动生成代码)~~ +- [x] ~~demo-graylog(集成 gralog 日志管理)~~ +- [ ] demo-sso(集成单点登录)参见 [ISSUE#12](https://github.com/xkcoding/spring-boot-demo/issues/12) +- [x] ~~demo-ldap (集成 ldap)参见 [ISSUE#23](https://github.com/xkcoding/spring-boot-demo/issues/23)~~ +- [x] ~~demo-dynamic-datasource(动态添加数据源,切换数据源)~~ +- [x] ~~demo-ratelimit-guava(单机限流保护API,集成 Guava 的 RateLimiter)~~ +- [x] ~~demo-ratelimit-redis(分布式限流保护API,使用 Redis + lua 脚本实现)~~ +- [x] ~~demo-https(集成 HTTPS)~~ +- [x] ~~demo-elasticsearch-rest-high-level-client(集成 Elasticsearch 7.x 版本,使用官方 rest high level client操作 ES 数据)~~ +- [ ] demo-springbatch(数据处理) +- [ ] demo-security-justauth(使用 JustAuth 登录 GitHub,使用 Security 管理登录状态) +- [x] ~~demo-flyway(集成 Flyway,项目启动时初始化数据库表结构,同时支持数据库脚本版本控制)~~ ## 备注 diff --git a/spring-boot-demo-activiti/.gitignore b/demo-activiti/.gitignore similarity index 100% rename from spring-boot-demo-activiti/.gitignore rename to demo-activiti/.gitignore diff --git a/demo-activiti/pom.xml b/demo-activiti/pom.xml new file mode 100644 index 0000000..e82420d --- /dev/null +++ b/demo-activiti/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + demo-activiti + 1.0.0-SNAPSHOT + jar + + demo-activiti + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + org.activiti + activiti-spring-boot-starter + 7.1.0.M2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + org.projectlombok + lombok + true + + + + + demo-activiti + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java b/demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java new file mode 100644 index 0000000..f8d5c99 --- /dev/null +++ b/demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.activiti; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-31 22:24 + */ +@SpringBootApplication +public class SpringBootDemoActivitiApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoActivitiApplication.class, args); + } + +} + diff --git a/demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java b/demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java new file mode 100644 index 0000000..5f46a2a --- /dev/null +++ b/demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java @@ -0,0 +1,56 @@ +package com.xkcoding.activiti.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 安全配置类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-07-01 18:40 + */ +@Slf4j +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService()); + } + + @Bean + protected UserDetailsService myUserDetailsService() { + InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); + + String[][] usersGroupsAndRoles = {{"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"}, {"admin", "password", "ROLE_ACTIVITI_ADMIN"}}; + + for (String[] user : usersGroupsAndRoles) { + List authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); + log.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); + inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()))); + } + + + return inMemoryUserDetailsManager; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java b/demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java new file mode 100755 index 0000000..33a6986 --- /dev/null +++ b/demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java @@ -0,0 +1,74 @@ +package com.xkcoding.activiti.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +/** + *

+ * 认证工具 + *

+ * + * @author yangkai.shen + * @date Created in 2019-07-01 18:38 + */ +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class SecurityUtil { + + private final UserDetailsService userDetailsService; + + public void logInAs(String username) { + + UserDetails user = userDetailsService.loadUserByUsername(username); + if (user == null) { + throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); + } + + SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() { + @Override + public Collection getAuthorities() { + return user.getAuthorities(); + } + + @Override + public Object getCredentials() { + return user.getPassword(); + } + + @Override + public Object getDetails() { + return user; + } + + @Override + public Object getPrincipal() { + return user; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + + } + + @Override + public String getName() { + return user.getUsername(); + } + })); + org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); + } +} diff --git a/spring-boot-demo-activiti/src/main/resources/application.yml b/demo-activiti/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-activiti/src/main/resources/application.yml rename to demo-activiti/src/main/resources/application.yml diff --git a/spring-boot-demo-activiti/src/main/resources/processes/team01.bpmn b/demo-activiti/src/main/resources/processes/team01.bpmn similarity index 100% rename from spring-boot-demo-activiti/src/main/resources/processes/team01.bpmn rename to demo-activiti/src/main/resources/processes/team01.bpmn diff --git a/spring-boot-demo-activiti/src/test/java/com/xkcoding/activiti/SpringBootDemoActivitiApplicationTests.java b/demo-activiti/src/test/java/com/xkcoding/activiti/SpringBootDemoActivitiApplicationTests.java similarity index 100% rename from spring-boot-demo-activiti/src/test/java/com/xkcoding/activiti/SpringBootDemoActivitiApplicationTests.java rename to demo-activiti/src/test/java/com/xkcoding/activiti/SpringBootDemoActivitiApplicationTests.java diff --git a/spring-boot-demo-actuator/.gitignore b/demo-actuator/.gitignore similarity index 100% rename from spring-boot-demo-actuator/.gitignore rename to demo-actuator/.gitignore diff --git a/spring-boot-demo-actuator/README.md b/demo-actuator/README.md similarity index 100% rename from spring-boot-demo-actuator/README.md rename to demo-actuator/README.md diff --git a/demo-actuator/pom.xml b/demo-actuator/pom.xml new file mode 100644 index 0000000..7fc6aef --- /dev/null +++ b/demo-actuator/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + demo-actuator + 1.0.0-SNAPSHOT + jar + + demo-actuator + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test + test + + + + + demo-actuator + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java b/demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java new file mode 100644 index 0000000..4630c8f --- /dev/null +++ b/demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.actuator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-9-29 14:27 + */ +@SpringBootApplication +public class SpringBootDemoActuatorApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoActuatorApplication.class, args); + } +} diff --git a/spring-boot-demo-actuator/src/main/resources/application.yml b/demo-actuator/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-actuator/src/main/resources/application.yml rename to demo-actuator/src/main/resources/application.yml diff --git a/demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java b/demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java new file mode 100644 index 0000000..ac2d387 --- /dev/null +++ b/demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.actuator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoActuatorApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-admin/README.md b/demo-admin/README.md similarity index 100% rename from spring-boot-demo-admin/README.md rename to demo-admin/README.md diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/.gitignore b/demo-admin/admin-client/.gitignore similarity index 100% rename from spring-boot-demo-admin/spring-boot-demo-admin-client/.gitignore rename to demo-admin/admin-client/.gitignore diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/README.md b/demo-admin/admin-client/README.md similarity index 100% rename from spring-boot-demo-admin/spring-boot-demo-admin-client/README.md rename to demo-admin/admin-client/README.md diff --git a/demo-admin/admin-client/pom.xml b/demo-admin/admin-client/pom.xml new file mode 100644 index 0000000..d5708b4 --- /dev/null +++ b/demo-admin/admin-client/pom.xml @@ -0,0 +1,57 @@ + + + + com.xkcoding + demo-admin + 1.0.0-SNAPSHOT + + 4.0.0 + + admin-client + 1.0.0-SNAPSHOT + jar + + admin-client + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + de.codecentric + spring-boot-admin-starter-client + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + admin-client + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java b/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java new file mode 100644 index 0000000..755bb81 --- /dev/null +++ b/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.admin.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-8 14:16 + */ +@SpringBootApplication +public class SpringBootDemoAdminClientApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoAdminClientApplication.class, args); + } +} diff --git a/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java b/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java new file mode 100644 index 0000000..98f68ac --- /dev/null +++ b/demo-admin/admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java @@ -0,0 +1,20 @@ +package com.xkcoding.admin.client.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 首页 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-08 14:15 + */ +@RestController +public class IndexController { + @GetMapping(value = {"", "/"}) + public String index() { + return "This is a Spring Boot Admin Client."; + } +} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/resources/application.yml b/demo-admin/admin-client/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/resources/application.yml rename to demo-admin/admin-client/src/main/resources/application.yml diff --git a/demo-admin/admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java b/demo-admin/admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java new file mode 100644 index 0000000..283f40a --- /dev/null +++ b/demo-admin/admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.admin.client; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoAdminClientApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/.gitignore b/demo-admin/admin-server/.gitignore similarity index 100% rename from spring-boot-demo-admin/spring-boot-demo-admin-server/.gitignore rename to demo-admin/admin-server/.gitignore diff --git a/demo-admin/admin-server/README.md b/demo-admin/admin-server/README.md new file mode 100644 index 0000000..f89cd31 --- /dev/null +++ b/demo-admin/admin-server/README.md @@ -0,0 +1,90 @@ +# spring-boot-demo-admin-server + +> 本 demo 主要演示了如何搭建一个 Spring Boot Admin 的服务端项目,可视化展示自己客户端项目的运行状态。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-admin-server + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-admin-server + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo-admin + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + de.codecentric + spring-boot-admin-starter-server + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-admin-server + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoAdminServerApplication.java + +```java +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-08 14:08 + */ +@EnableAdminServer +@SpringBootApplication +public class SpringBootDemoAdminServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoAdminServerApplication.class, args); + } +} +``` + +## application.yml + +```yaml +server: + port: 8000 +``` + diff --git a/demo-admin/admin-server/pom.xml b/demo-admin/admin-server/pom.xml new file mode 100644 index 0000000..0e9f7d6 --- /dev/null +++ b/demo-admin/admin-server/pom.xml @@ -0,0 +1,52 @@ + + + + com.xkcoding + demo-admin + 1.0.0-SNAPSHOT + + 4.0.0 + + demo-admin-server + 1.0.0-SNAPSHOT + jar + + admin-server + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + de.codecentric + spring-boot-admin-starter-server + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + admin-server + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-admin/admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java b/demo-admin/admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java new file mode 100644 index 0000000..5bf7bad --- /dev/null +++ b/demo-admin/admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.admin.server; + +import de.codecentric.boot.admin.server.config.EnableAdminServer; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-08 14:08 + */ +@EnableAdminServer +@SpringBootApplication +public class SpringBootDemoAdminServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoAdminServerApplication.class, args); + } +} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/src/main/resources/application.yml b/demo-admin/admin-server/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-admin/spring-boot-demo-admin-server/src/main/resources/application.yml rename to demo-admin/admin-server/src/main/resources/application.yml diff --git a/demo-admin/admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java b/demo-admin/admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java new file mode 100644 index 0000000..d70d4f8 --- /dev/null +++ b/demo-admin/admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.admin.server; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoAdminServerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/demo-admin/pom.xml b/demo-admin/pom.xml new file mode 100644 index 0000000..eccaa84 --- /dev/null +++ b/demo-admin/pom.xml @@ -0,0 +1,36 @@ + + + + spring-boot-demo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + demo-admin + pom + + + 2.1.0 + + + + admin-client + admin-server + + + + + + de.codecentric + spring-boot-admin-dependencies + ${spring-boot-admin.version} + pom + import + + + + + diff --git a/spring-boot-demo-async/.gitignore b/demo-async/.gitignore similarity index 100% rename from spring-boot-demo-async/.gitignore rename to demo-async/.gitignore diff --git a/demo-async/README.md b/demo-async/README.md new file mode 100644 index 0000000..4561544 --- /dev/null +++ b/demo-async/README.md @@ -0,0 +1,257 @@ +# spring-boot-demo-async + +> 此 demo 主要演示了 Spring Boot 如何使用原生提供的异步任务支持,实现异步执行任务。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-async + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-async + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-async + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + task: + execution: + pool: + # 最大线程数 + max-size: 16 + # 核心线程数 + core-size: 16 + # 存活时间 + keep-alive: 10s + # 队列大小 + queue-capacity: 100 + # 是否允许核心线程超时 + allow-core-thread-timeout: true + # 线程名称前缀 + thread-name-prefix: async-task- +``` + +## SpringBootDemoAsyncApplication.java + +```java +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:28 + */ +@EnableAsync +@SpringBootApplication +public class SpringBootDemoAsyncApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoAsyncApplication.class, args); + } + +} +``` + +## TaskFactory.java + +```java +/** + *

+ * 任务工厂 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:37 + */ +@Component +@Slf4j +public class TaskFactory { + + /** + * 模拟5秒的异步任务 + */ + @Async + public Future asyncTask1() throws InterruptedException { + doTask("asyncTask1", 5); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟2秒的异步任务 + */ + @Async + public Future asyncTask2() throws InterruptedException { + doTask("asyncTask2", 2); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟3秒的异步任务 + */ + @Async + public Future asyncTask3() throws InterruptedException { + doTask("asyncTask3", 3); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟5秒的同步任务 + */ + public void task1() throws InterruptedException { + doTask("task1", 5); + } + + /** + * 模拟2秒的同步任务 + */ + public void task2() throws InterruptedException { + doTask("task2", 2); + } + + /** + * 模拟3秒的同步任务 + */ + public void task3() throws InterruptedException { + doTask("task3", 3); + } + + private void doTask(String taskName, Integer time) throws InterruptedException { + log.info("{}开始执行,当前线程名称【{}】", taskName, Thread.currentThread().getName()); + TimeUnit.SECONDS.sleep(time); + log.info("{}执行成功,当前线程名称【{}】", taskName, Thread.currentThread().getName()); + } +} +``` + +## TaskFactoryTest.java + +```java +/** + *

+ * 测试任务 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:49 + */ +@Slf4j +public class TaskFactoryTest extends SpringBootDemoAsyncApplicationTests { + @Autowired + private TaskFactory task; + + /** + * 测试异步任务 + */ + @Test + public void asyncTaskTest() throws InterruptedException, ExecutionException { + long start = System.currentTimeMillis(); + Future asyncTask1 = task.asyncTask1(); + Future asyncTask2 = task.asyncTask2(); + Future asyncTask3 = task.asyncTask3(); + + // 调用 get() 阻塞主线程 + asyncTask1.get(); + asyncTask2.get(); + asyncTask3.get(); + long end = System.currentTimeMillis(); + + log.info("异步任务全部执行结束,总耗时:{} 毫秒", (end - start)); + } + + /** + * 测试同步任务 + */ + @Test + public void taskTest() throws InterruptedException { + long start = System.currentTimeMillis(); + task.task1(); + task.task2(); + task.task3(); + long end = System.currentTimeMillis(); + + log.info("同步任务全部执行结束,总耗时:{} 毫秒", (end - start)); + } +} +``` + +## 运行结果 + +### 异步任务 + +```bash +2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-3] com.xkcoding.async.task.TaskFactory : asyncTask3开始执行,当前线程名称【async-task-3】 +2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-1] com.xkcoding.async.task.TaskFactory : asyncTask1开始执行,当前线程名称【async-task-1】 +2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-2] com.xkcoding.async.task.TaskFactory : asyncTask2开始执行,当前线程名称【async-task-2】 +2018-12-29 10:57:30.514 INFO 3134 --- [ async-task-2] com.xkcoding.async.task.TaskFactory : asyncTask2执行成功,当前线程名称【async-task-2】 +2018-12-29 10:57:31.516 INFO 3134 --- [ async-task-3] com.xkcoding.async.task.TaskFactory : asyncTask3执行成功,当前线程名称【async-task-3】 +2018-12-29 10:57:33.517 INFO 3134 --- [ async-task-1] com.xkcoding.async.task.TaskFactory : asyncTask1执行成功,当前线程名称【async-task-1】 +2018-12-29 10:57:33.517 INFO 3134 --- [ main] com.xkcoding.async.task.TaskFactoryTest : 异步任务全部执行结束,总耗时:5015 毫秒 +``` + +### 同步任务 + +```bash +2018-12-29 10:55:49.830 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task1开始执行,当前线程名称【main】 +2018-12-29 10:55:54.834 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task1执行成功,当前线程名称【main】 +2018-12-29 10:55:54.835 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task2开始执行,当前线程名称【main】 +2018-12-29 10:55:56.839 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task2执行成功,当前线程名称【main】 +2018-12-29 10:55:56.839 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task3开始执行,当前线程名称【main】 +2018-12-29 10:55:59.843 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task3执行成功,当前线程名称【main】 +2018-12-29 10:55:59.843 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactoryTest : 同步任务全部执行结束,总耗时:10023 毫秒 +``` + +## 参考 + +- Spring Boot 异步任务线程池的配置 参考官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-task-execution-scheduling diff --git a/demo-async/pom.xml b/demo-async/pom.xml new file mode 100644 index 0000000..388e38b --- /dev/null +++ b/demo-async/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + demo-async + 1.0.0-SNAPSHOT + jar + + demo-async + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-async + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java b/demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java new file mode 100644 index 0000000..6d1e8e8 --- /dev/null +++ b/demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java @@ -0,0 +1,24 @@ +package com.xkcoding.async; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:28 + */ +@EnableAsync +@SpringBootApplication +public class SpringBootDemoAsyncApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoAsyncApplication.class, args); + } + +} + diff --git a/demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java b/demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java new file mode 100644 index 0000000..e492110 --- /dev/null +++ b/demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java @@ -0,0 +1,76 @@ +package com.xkcoding.async.task; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + *

+ * 任务工厂 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:37 + */ +@Component +@Slf4j +public class TaskFactory { + + /** + * 模拟5秒的异步任务 + */ + @Async + public Future asyncTask1() throws InterruptedException { + doTask("asyncTask1", 5); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟2秒的异步任务 + */ + @Async + public Future asyncTask2() throws InterruptedException { + doTask("asyncTask2", 2); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟3秒的异步任务 + */ + @Async + public Future asyncTask3() throws InterruptedException { + doTask("asyncTask3", 3); + return new AsyncResult<>(Boolean.TRUE); + } + + /** + * 模拟5秒的同步任务 + */ + public void task1() throws InterruptedException { + doTask("task1", 5); + } + + /** + * 模拟2秒的同步任务 + */ + public void task2() throws InterruptedException { + doTask("task2", 2); + } + + /** + * 模拟3秒的同步任务 + */ + public void task3() throws InterruptedException { + doTask("task3", 3); + } + + private void doTask(String taskName, Integer time) throws InterruptedException { + log.info("{}开始执行,当前线程名称【{}】", taskName, Thread.currentThread().getName()); + TimeUnit.SECONDS.sleep(time); + log.info("{}执行成功,当前线程名称【{}】", taskName, Thread.currentThread().getName()); + } +} diff --git a/spring-boot-demo-async/src/main/resources/application.yml b/demo-async/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-async/src/main/resources/application.yml rename to demo-async/src/main/resources/application.yml diff --git a/spring-boot-demo-async/src/test/java/com/xkcoding/async/SpringBootDemoAsyncApplicationTests.java b/demo-async/src/test/java/com/xkcoding/async/SpringBootDemoAsyncApplicationTests.java similarity index 100% rename from spring-boot-demo-async/src/test/java/com/xkcoding/async/SpringBootDemoAsyncApplicationTests.java rename to demo-async/src/test/java/com/xkcoding/async/SpringBootDemoAsyncApplicationTests.java diff --git a/demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java b/demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java new file mode 100644 index 0000000..89a226f --- /dev/null +++ b/demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java @@ -0,0 +1,56 @@ +package com.xkcoding.async.task; + +import com.xkcoding.async.SpringBootDemoAsyncApplicationTests; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +/** + *

+ * 测试任务 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 10:49 + */ +@Slf4j +public class TaskFactoryTest extends SpringBootDemoAsyncApplicationTests { + @Autowired + private TaskFactory task; + + /** + * 测试异步任务 + */ + @Test + public void asyncTaskTest() throws InterruptedException, ExecutionException { + long start = System.currentTimeMillis(); + Future asyncTask1 = task.asyncTask1(); + Future asyncTask2 = task.asyncTask2(); + Future asyncTask3 = task.asyncTask3(); + + // 调用 get() 阻塞主线程 + asyncTask1.get(); + asyncTask2.get(); + asyncTask3.get(); + long end = System.currentTimeMillis(); + + log.info("异步任务全部执行结束,总耗时:{} 毫秒", (end - start)); + } + + /** + * 测试同步任务 + */ + @Test + public void taskTest() throws InterruptedException { + long start = System.currentTimeMillis(); + task.task1(); + task.task2(); + task.task3(); + long end = System.currentTimeMillis(); + + log.info("同步任务全部执行结束,总耗时:{} 毫秒", (end - start)); + } +} diff --git a/spring-boot-demo-cache-ehcache/.gitignore b/demo-cache-ehcache/.gitignore similarity index 100% rename from spring-boot-demo-cache-ehcache/.gitignore rename to demo-cache-ehcache/.gitignore diff --git a/demo-cache-ehcache/README.md b/demo-cache-ehcache/README.md new file mode 100644 index 0000000..2d99cc6 --- /dev/null +++ b/demo-cache-ehcache/README.md @@ -0,0 +1,286 @@ +# spring-boot-demo-cache-ehcache + +> 此 demo 主要演示了 Spring Boot 如何集成 ehcache 使用缓存。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-cache-ehcache + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-cache-ehcache + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-cache + + + + net.sf.ehcache + ehcache + + + + org.projectlombok + lombok + true + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-cache-ehcache + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoCacheEhcacheApplication.java + +```java +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 17:02 + */ +@SpringBootApplication +@EnableCaching +public class SpringBootDemoCacheEhcacheApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args); + } +} +``` + +## application.yml + +```yaml +spring: + cache: + type: ehcache + ehcache: + config: classpath:ehcache.xml +logging: + level: + com.xkcoding: debug +``` + +## ehcache.xml + +```xml + + + + + + + + + + + +``` + +## UserServiceImpl.java + +```java +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:54 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} +``` + +## UserServiceTest.java + +```java +/** + *

+ * ehcache缓存测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:58 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheEhcacheApplicationTests { + + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "user4")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使ehcache中存在缓存数据 + userService.get(1L); + // 删除,查看ehcache是否存在缓存数据 + userService.delete(1L); + } +} +``` + +## 参考 + +- Ehcache 官网:http://www.ehcache.org/documentation/ +- Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-caching-provider-ehcache2 +- 博客:https://juejin.im/post/5b308de9518825748b56ae1d diff --git a/demo-cache-ehcache/pom.xml b/demo-cache-ehcache/pom.xml new file mode 100644 index 0000000..8a44447 --- /dev/null +++ b/demo-cache-ehcache/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-cache-ehcache + 1.0.0-SNAPSHOT + jar + + demo-cache-ehcache + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-cache + + + + net.sf.ehcache + ehcache + + + + org.projectlombok + lombok + true + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-cache-ehcache + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java new file mode 100644 index 0000000..2fdf43f --- /dev/null +++ b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.cache.ehcache; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 17:02 + */ +@SpringBootApplication +@EnableCaching +public class SpringBootDemoCacheEhcacheApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args); + } +} diff --git a/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java new file mode 100644 index 0000000..522357b --- /dev/null +++ b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java @@ -0,0 +1,30 @@ +package com.xkcoding.cache.ehcache.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

+ * 用户实体 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:53 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class User implements Serializable { + private static final long serialVersionUID = 2892248514883451461L; + /** + * 主键id + */ + private Long id; + /** + * 姓名 + */ + private String name; +} diff --git a/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java new file mode 100644 index 0000000..79fc0f4 --- /dev/null +++ b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java @@ -0,0 +1,36 @@ +package com.xkcoding.cache.ehcache.service; + +import com.xkcoding.cache.ehcache.entity.User; + +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:53 + */ +public interface UserService { + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + User saveOrUpdate(User user); + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + User get(Long id); + + /** + * 删除 + * + * @param id key值 + */ + void delete(Long id); +} diff --git a/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..a013ba7 --- /dev/null +++ b/demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java @@ -0,0 +1,78 @@ +package com.xkcoding.cache.ehcache.service.impl; + +import com.google.common.collect.Maps; +import com.xkcoding.cache.ehcache.entity.User; +import com.xkcoding.cache.ehcache.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:54 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} diff --git a/spring-boot-demo-cache-ehcache/src/main/resources/application.yml b/demo-cache-ehcache/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-cache-ehcache/src/main/resources/application.yml rename to demo-cache-ehcache/src/main/resources/application.yml diff --git a/spring-boot-demo-cache-ehcache/src/main/resources/ehcache.xml b/demo-cache-ehcache/src/main/resources/ehcache.xml similarity index 100% rename from spring-boot-demo-cache-ehcache/src/main/resources/ehcache.xml rename to demo-cache-ehcache/src/main/resources/ehcache.xml diff --git a/spring-boot-demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplicationTests.java b/demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplicationTests.java similarity index 100% rename from spring-boot-demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplicationTests.java rename to demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplicationTests.java diff --git a/demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java b/demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java new file mode 100644 index 0000000..00d3b0f --- /dev/null +++ b/demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java @@ -0,0 +1,60 @@ +package com.xkcoding.cache.ehcache.service; + +import com.xkcoding.cache.ehcache.SpringBootDemoCacheEhcacheApplicationTests; +import com.xkcoding.cache.ehcache.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

+ * ehcache缓存测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-16 16:58 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheEhcacheApplicationTests { + + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "user4")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使ehcache中存在缓存数据 + userService.get(1L); + // 删除,查看ehcache是否存在缓存数据 + userService.delete(1L); + } +} diff --git a/spring-boot-demo-cache-redis/.gitignore b/demo-cache-redis/.gitignore similarity index 100% rename from spring-boot-demo-cache-redis/.gitignore rename to demo-cache-redis/.gitignore diff --git a/demo-cache-redis/README.md b/demo-cache-redis/README.md new file mode 100644 index 0000000..7baebf5 --- /dev/null +++ b/demo-cache-redis/README.md @@ -0,0 +1,347 @@ +# spring-boot-demo-cache-redis + +> 此 demo 主要演示了 Spring Boot 如何整合 redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-cache-redis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-cache-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-starter-json + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-cache-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis +logging: + level: + com.xkcoding: debug +``` + +## RedisConfig.java + +```java +/** + *

+ * redis配置 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:41 + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式 + */ + @Bean + public CacheManager cacheManager(RedisConnectionFactory factory) { + // 配置序列化 + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); + RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); + } +} +``` + +## UserServiceImpl.java + +```java +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:45 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} +``` + +## RedisTest.java + +> 主要测试使用 `RedisTemplate` 操作 `Redis` 中的数据: +> +> - opsForValue:对应 String(字符串) +> - opsForZSet:对应 ZSet(有序集合) +> - opsForHash:对应 Hash(哈希) +> - opsForList:对应 List(列表) +> - opsForSet:对应 Set(集合) +> - opsForGeo:** 对应 GEO(地理位置) + +```java +/** + *

+ * Redis测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 17:17 + */ +@Slf4j +public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private RedisTemplate redisCacheTemplate; + + /** + * 测试 Redis 操作 + */ + @Test + public void get() { + // 测试线程安全,程序结束查看redis中count的值是否为1000 + ExecutorService executorService = Executors.newFixedThreadPool(1000); + IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); + + stringRedisTemplate.opsForValue().set("k1", "v1"); + String k1 = stringRedisTemplate.opsForValue().get("k1"); + log.debug("【k1】= {}", k1); + + // 以下演示整合,具体Redis命令可以参考官方文档 + String key = "xkcoding:user:1"; + redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); + // 对应 String(字符串) + User user = (User) redisCacheTemplate.opsForValue().get(key); + log.debug("【user】= {}", user); + } +} + +``` + +## UserServiceTest.java + +> 主要测试使用Redis缓存是否起效 + +```java +/** + *

+ * Redis - 缓存测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:53 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "测试中文")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使redis中存在缓存数据 + userService.get(1L); + // 删除,查看redis是否存在缓存数据 + userService.delete(1L); + } + +} +``` + +## 参考 + +- spring-data-redis 官方文档:https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/ +- redis 文档:https://redis.io/documentation +- redis 中文文档:http://www.redis.cn/commands.html diff --git a/demo-cache-redis/pom.xml b/demo-cache-redis/pom.xml new file mode 100644 index 0000000..5f11beb --- /dev/null +++ b/demo-cache-redis/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + demo-cache-redis + 1.0.0-SNAPSHOT + jar + + demo-cache-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-starter-json + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + demo-cache-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplication.java b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplication.java similarity index 100% rename from spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplication.java rename to demo-cache-redis/src/main/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplication.java diff --git a/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java new file mode 100644 index 0000000..dae7aed --- /dev/null +++ b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java @@ -0,0 +1,56 @@ +package com.xkcoding.cache.redis.config; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.io.Serializable; + +/** + *

+ * redis配置 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:41 + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式 + */ + @Bean + public CacheManager cacheManager(RedisConnectionFactory factory) { + // 配置序列化 + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); + RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); + } +} diff --git a/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java new file mode 100644 index 0000000..f3128e8 --- /dev/null +++ b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java @@ -0,0 +1,30 @@ +package com.xkcoding.cache.redis.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

+ * 用户实体 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:39 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class User implements Serializable { + private static final long serialVersionUID = 2892248514883451461L; + /** + * 主键id + */ + private Long id; + /** + * 姓名 + */ + private String name; +} diff --git a/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java new file mode 100644 index 0000000..331901b --- /dev/null +++ b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java @@ -0,0 +1,36 @@ +package com.xkcoding.cache.redis.service; + +import com.xkcoding.cache.redis.entity.User; + +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:45 + */ +public interface UserService { + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + User saveOrUpdate(User user); + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + User get(Long id); + + /** + * 删除 + * + * @param id key值 + */ + void delete(Long id); +} diff --git a/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..f7b7d1f --- /dev/null +++ b/demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java @@ -0,0 +1,78 @@ +package com.xkcoding.cache.redis.service.impl; + +import com.google.common.collect.Maps; +import com.xkcoding.cache.redis.entity.User; +import com.xkcoding.cache.redis.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + *

+ * UserService + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:45 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} diff --git a/spring-boot-demo-cache-redis/src/main/resources/application.yml b/demo-cache-redis/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-cache-redis/src/main/resources/application.yml rename to demo-cache-redis/src/main/resources/application.yml diff --git a/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java b/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java new file mode 100644 index 0000000..8389ea5 --- /dev/null +++ b/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java @@ -0,0 +1,52 @@ +package com.xkcoding.cache.redis; + +import com.xkcoding.cache.redis.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.io.Serializable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.IntStream; + +/** + *

+ * Redis测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 17:17 + */ +@Slf4j +public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private RedisTemplate redisCacheTemplate; + + /** + * 测试 Redis 操作 + */ + @Test + public void get() { + // 测试线程安全,程序结束查看redis中count的值是否为1000 + ExecutorService executorService = Executors.newFixedThreadPool(1000); + IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); + + stringRedisTemplate.opsForValue().set("k1", "v1"); + String k1 = stringRedisTemplate.opsForValue().get("k1"); + log.debug("【k1】= {}", k1); + + // 以下演示整合,具体Redis命令可以参考官方文档 + String key = "xkcoding:user:1"; + redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); + // 对应 String(字符串) + User user = (User) redisCacheTemplate.opsForValue().get(key); + log.debug("【user】= {}", user); + } +} diff --git a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplicationTests.java b/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplicationTests.java similarity index 100% rename from spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplicationTests.java rename to demo-cache-redis/src/test/java/com/xkcoding/cache/redis/SpringBootDemoCacheRedisApplicationTests.java diff --git a/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java b/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java new file mode 100644 index 0000000..3318787 --- /dev/null +++ b/demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java @@ -0,0 +1,60 @@ +package com.xkcoding.cache.redis.service; + +import com.xkcoding.cache.redis.SpringBootDemoCacheRedisApplicationTests; +import com.xkcoding.cache.redis.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

+ * Redis - 缓存测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-15 16:53 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "测试中文")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使redis中存在缓存数据 + userService.get(1L); + // 删除,查看redis是否存在缓存数据 + userService.delete(1L); + } + +} diff --git a/spring-boot-demo-codegen/.gitignore b/demo-codegen/.gitignore similarity index 100% rename from spring-boot-demo-codegen/.gitignore rename to demo-codegen/.gitignore diff --git a/demo-codegen/README.md b/demo-codegen/README.md new file mode 100644 index 0000000..ce635c2 --- /dev/null +++ b/demo-codegen/README.md @@ -0,0 +1,410 @@ +# spring-boot-demo-codegen + +> 此 demo 主要演示了 Spring Boot 使用**模板技术**生成代码,并提供前端页面,可生成 Entity/Mapper/Service/Controller 等代码。 + +## 1. 主要功能 + +1. 使用 `velocity` 代码生成 +2. 暂时支持mysql数据库的代码生成 +3. 提供前端页面展示,并下载代码压缩包 + +> 注意:① Entity里使用lombok,简化代码 ② Mapper 和 Service 层集成 Mybatis-Plus 简化代码 + +## 2. 运行 + +1. 运行 `SpringBootDemoCodegenApplication` 启动项目 +2. 打开浏览器,输入 http://localhost:8080/demo/index.html +3. 输入查询条件,生成代码 + +## 3. 关键代码 + +### 3.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-codegen + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-codegen + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.zaxxer + HikariCP + + + + + org.apache.velocity + velocity + 1.7 + + + + org.apache.commons + commons-text + 1.6 + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-codegen + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 3.2. 代码生成器配置 + +```properties +#代码生成器,配置信息 +mainPath=com.xkcoding +#包名 +package=com.xkcoding +moduleName=generator +#作者 +author=Yangkai.Shen +#表前缀(类名不会包含表前缀) +tablePrefix=tb_ +#类型转换,配置信息 +tinyint=Integer +smallint=Integer +mediumint=Integer +int=Integer +integer=Integer +bigint=Long +float=Float +double=Double +decimal=BigDecimal +bit=Boolean +char=String +varchar=String +tinytext=String +text=String +mediumtext=String +longtext=String +date=LocalDateTime +datetime=LocalDateTime +timestamp=LocalDateTime +``` + +### 3.3. CodeGenUtil.java + +```java +/** + *

+ * 代码生成器 工具类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:27 + */ +@Slf4j +@UtilityClass +public class CodeGenUtil { + + private final String ENTITY_JAVA_VM = "Entity.java.vm"; + private final String MAPPER_JAVA_VM = "Mapper.java.vm"; + private final String SERVICE_JAVA_VM = "Service.java.vm"; + private final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm"; + private final String CONTROLLER_JAVA_VM = "Controller.java.vm"; + private final String MAPPER_XML_VM = "Mapper.xml.vm"; + private final String API_JS_VM = "api.js.vm"; + + private List getTemplates() { + List templates = new ArrayList<>(); + templates.add("template/Entity.java.vm"); + templates.add("template/Mapper.java.vm"); + templates.add("template/Mapper.xml.vm"); + templates.add("template/Service.java.vm"); + templates.add("template/ServiceImpl.java.vm"); + templates.add("template/Controller.java.vm"); + + templates.add("template/api.js.vm"); + return templates; + } + + /** + * 生成代码 + */ + public void generatorCode(GenConfig genConfig, Entity table, List columns, ZipOutputStream zip) { + //配置信息 + Props props = getConfig(); + boolean hasBigDecimal = false; + //表信息 + TableEntity tableEntity = new TableEntity(); + tableEntity.setTableName(table.getStr("tableName")); + + if (StrUtil.isNotBlank(genConfig.getComments())) { + tableEntity.setComments(genConfig.getComments()); + } else { + tableEntity.setComments(table.getStr("tableComment")); + } + + String tablePrefix; + if (StrUtil.isNotBlank(genConfig.getTablePrefix())) { + tablePrefix = genConfig.getTablePrefix(); + } else { + tablePrefix = props.getStr("tablePrefix"); + } + + //表名转换成Java类名 + String className = tableToJava(tableEntity.getTableName(), tablePrefix); + tableEntity.setCaseClassName(className); + tableEntity.setLowerClassName(StrUtil.lowerFirst(className)); + + //列信息 + List columnList = Lists.newArrayList(); + for (Entity column : columns) { + ColumnEntity columnEntity = new ColumnEntity(); + columnEntity.setColumnName(column.getStr("columnName")); + columnEntity.setDataType(column.getStr("dataType")); + columnEntity.setComments(column.getStr("columnComment")); + columnEntity.setExtra(column.getStr("extra")); + + //列名转换成Java属性名 + String attrName = columnToJava(columnEntity.getColumnName()); + columnEntity.setCaseAttrName(attrName); + columnEntity.setLowerAttrName(StrUtil.lowerFirst(attrName)); + + //列的数据类型,转换成Java类型 + String attrType = props.getStr(columnEntity.getDataType(), "unknownType"); + columnEntity.setAttrType(attrType); + if (!hasBigDecimal && "BigDecimal".equals(attrType)) { + hasBigDecimal = true; + } + //是否主键 + if ("PRI".equalsIgnoreCase(column.getStr("columnKey")) && tableEntity.getPk() == null) { + tableEntity.setPk(columnEntity); + } + + columnList.add(columnEntity); + } + tableEntity.setColumns(columnList); + + //没主键,则第一个字段为主键 + if (tableEntity.getPk() == null) { + tableEntity.setPk(tableEntity.getColumns().get(0)); + } + + //设置velocity资源加载器 + Properties prop = new Properties(); + prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + Velocity.init(prop); + //封装模板数据 + Map map = new HashMap<>(16); + map.put("tableName", tableEntity.getTableName()); + map.put("pk", tableEntity.getPk()); + map.put("className", tableEntity.getCaseClassName()); + map.put("classname", tableEntity.getLowerClassName()); + map.put("pathName", tableEntity.getLowerClassName().toLowerCase()); + map.put("columns", tableEntity.getColumns()); + map.put("hasBigDecimal", hasBigDecimal); + map.put("datetime", DateUtil.now()); + map.put("year", DateUtil.year(new Date())); + + if (StrUtil.isNotBlank(genConfig.getComments())) { + map.put("comments", genConfig.getComments()); + } else { + map.put("comments", tableEntity.getComments()); + } + + if (StrUtil.isNotBlank(genConfig.getAuthor())) { + map.put("author", genConfig.getAuthor()); + } else { + map.put("author", props.getStr("author")); + } + + if (StrUtil.isNotBlank(genConfig.getModuleName())) { + map.put("moduleName", genConfig.getModuleName()); + } else { + map.put("moduleName", props.getStr("moduleName")); + } + + if (StrUtil.isNotBlank(genConfig.getPackageName())) { + map.put("package", genConfig.getPackageName()); + map.put("mainPath", genConfig.getPackageName()); + } else { + map.put("package", props.getStr("package")); + map.put("mainPath", props.getStr("mainPath")); + } + VelocityContext context = new VelocityContext(map); + + //获取模板列表 + List templates = getTemplates(); + for (String template : templates) { + //渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8); + tpl.merge(context, sw); + + try { + //添加到zip + zip.putNextEntry(new ZipEntry(Objects.requireNonNull(getFileName(template, tableEntity.getCaseClassName(), map + .get("package") + .toString(), map.get("moduleName").toString())))); + IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString()); + IoUtil.close(sw); + zip.closeEntry(); + } catch (IOException e) { + throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e); + } + } + } + + + /** + * 列名转换成Java属性名 + */ + private String columnToJava(String columnName) { + return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", ""); + } + + /** + * 表名转换成Java类名 + */ + private String tableToJava(String tableName, String tablePrefix) { + if (StrUtil.isNotBlank(tablePrefix)) { + tableName = tableName.replaceFirst(tablePrefix, ""); + } + return columnToJava(tableName); + } + + /** + * 获取配置信息 + */ + private Props getConfig() { + Props props = new Props("generator.properties"); + props.autoLoad(true); + return props; + } + + /** + * 获取文件名 + */ + private String getFileName(String template, String className, String packageName, String moduleName) { + // 包路径 + String packagePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; + // 资源路径 + String resourcePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator; + // api路径 + String apiPath = GenConstants.SIGNATURE + File.separator + "api" + File.separator; + + if (StrUtil.isNotBlank(packageName)) { + packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator; + } + + if (template.contains(ENTITY_JAVA_VM)) { + return packagePath + "entity" + File.separator + className + ".java"; + } + + if (template.contains(MAPPER_JAVA_VM)) { + return packagePath + "mapper" + File.separator + className + "Mapper.java"; + } + + if (template.contains(SERVICE_JAVA_VM)) { + return packagePath + "service" + File.separator + className + "Service.java"; + } + + if (template.contains(SERVICE_IMPL_JAVA_VM)) { + return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; + } + + if (template.contains(CONTROLLER_JAVA_VM)) { + return packagePath + "controller" + File.separator + className + "Controller.java"; + } + + if (template.contains(MAPPER_XML_VM)) { + return resourcePath + "mapper" + File.separator + className + "Mapper.xml"; + } + + if (template.contains(API_JS_VM)) { + return apiPath + className.toLowerCase() + ".js"; + } + + return null; + } +} +``` + +### 3.4. 其余代码参见demo + +## 4. 演示 + + + +## 5. 参考 + +- [基于人人开源 自动构建项目_V1](https://qq343509740.gitee.io/2018/12/20/%E7%AC%94%E8%AE%B0/%E8%87%AA%E5%8A%A8%E6%9E%84%E5%BB%BA%E9%A1%B9%E7%9B%AE/%E5%9F%BA%E4%BA%8E%E4%BA%BA%E4%BA%BA%E5%BC%80%E6%BA%90%20%E8%87%AA%E5%8A%A8%E6%9E%84%E5%BB%BA%E9%A1%B9%E7%9B%AE_V1/) + +- [Mybatis-Plus代码生成器](https://mybatis.plus/guide/generator.html#%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96) diff --git a/demo-codegen/pom.xml b/demo-codegen/pom.xml new file mode 100644 index 0000000..eeefe12 --- /dev/null +++ b/demo-codegen/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + demo-codegen + 1.0.0-SNAPSHOT + jar + + demo-codegen + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.zaxxer + HikariCP + + + + + org.apache.velocity + velocity-engine-core + 2.1 + + + + org.apache.commons + commons-text + 1.6 + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-codegen + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java b/demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java new file mode 100644 index 0000000..6d3b118 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.codegen; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:10 + */ +@SpringBootApplication +public class SpringBootDemoCodegenApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoCodegenApplication.class, args); + } + +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java b/demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java new file mode 100644 index 0000000..d2b1108 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java @@ -0,0 +1,25 @@ +package com.xkcoding.codegen.common; + +/** + *

+ * 统一状态码接口 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-21 16:28 + */ +public interface IResultCode { + /** + * 获取状态码 + * + * @return 状态码 + */ + Integer getCode(); + + /** + * 获取返回消息 + * + * @return 返回消息 + */ + String getMessage(); +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java b/demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java new file mode 100644 index 0000000..f05de4a --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java @@ -0,0 +1,38 @@ +package com.xkcoding.codegen.common; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + *

+ * 分页结果集 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 11:24 + */ +@Data +@AllArgsConstructor +public class PageResult { + /** + * 总条数 + */ + private Long total; + + /** + * 页码 + */ + private int pageNumber; + + /** + * 每页结果数 + */ + private int pageSize; + + /** + * 结果集 + */ + private List list; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java b/demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java new file mode 100644 index 0000000..af2a338 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java @@ -0,0 +1,90 @@ +package com.xkcoding.codegen.common; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 统一API对象返回 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:13 + */ +@Data +@NoArgsConstructor +public class R { + /** + * 状态码 + */ + private Integer code; + + /** + * 返回消息 + */ + private String message; + + /** + * 状态 + */ + private boolean status; + + /** + * 返回数据 + */ + private T data; + + public R(Integer code, String message, boolean status, T data) { + this.code = code; + this.message = message; + this.status = status; + this.data = data; + } + + public R(IResultCode resultCode, boolean status, T data) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + this.status = status; + this.data = data; + } + + public R(IResultCode resultCode, boolean status) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + this.status = status; + this.data = null; + } + + public static R success() { + return new R<>(ResultCode.OK, true); + } + + public static R message(String message) { + return new R<>(ResultCode.OK.getCode(), message, true, null); + } + + public static R success(T data) { + return new R<>(ResultCode.OK, true, data); + } + + public static R fail() { + return new R<>(ResultCode.ERROR, false); + } + + public static R fail(IResultCode resultCode) { + return new R<>(resultCode, false); + } + + public static R fail(Integer code, String message) { + return new R<>(code, message, false, null); + } + + public static R fail(IResultCode resultCode, T data) { + return new R<>(resultCode, false, data); + } + + public static R fail(Integer code, String message, T data) { + return new R<>(code, message, false, data); + } + +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java b/demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java new file mode 100644 index 0000000..5f06f3e --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java @@ -0,0 +1,39 @@ +package com.xkcoding.codegen.common; + +import lombok.Getter; + +/** + *

+ * 通用状态枚举 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:13 + */ +@Getter +public enum ResultCode implements IResultCode { + /** + * 成功 + */ + OK(200, "成功"), + /** + * 失败 + */ + ERROR(500, "失败"); + + /** + * 返回码 + */ + private Integer code; + + /** + * 返回消息 + */ + private String message; + + ResultCode(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java b/demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java new file mode 100644 index 0000000..6959cbc --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java @@ -0,0 +1,16 @@ +package com.xkcoding.codegen.constants; + +/** + *

+ * 常量池 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:04 + */ +public interface GenConstants { + /** + * 签名 + */ + String SIGNATURE = "xkcoding代码生成"; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java b/demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java new file mode 100755 index 0000000..879e546 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java @@ -0,0 +1,55 @@ +package com.xkcoding.codegen.controller; + +import cn.hutool.core.io.IoUtil; +import com.xkcoding.codegen.common.R; +import com.xkcoding.codegen.entity.GenConfig; +import com.xkcoding.codegen.entity.TableRequest; +import com.xkcoding.codegen.service.CodeGenService; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; + +/** + *

+ * 代码生成器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:11 + */ +@RestController +@AllArgsConstructor +@RequestMapping("/generator") +public class CodeGenController { + private final CodeGenService codeGenService; + + /** + * 列表 + * + * @param request 参数集 + * @return 数据库表 + */ + @GetMapping("/table") + public R listTables(TableRequest request) { + return R.success(codeGenService.listTables(request)); + } + + /** + * 生成代码 + */ + @SneakyThrows + @PostMapping("") + public void generatorCode(@RequestBody GenConfig genConfig, HttpServletResponse response) { + byte[] data = codeGenService.generatorCode(genConfig); + + response.reset(); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s.zip", genConfig.getTableName())); + response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length)); + response.setContentType("application/octet-stream; charset=UTF-8"); + + IoUtil.write(response.getOutputStream(), Boolean.TRUE, data); + } +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java new file mode 100755 index 0000000..1c1e1aa --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java @@ -0,0 +1,47 @@ +package com.xkcoding.codegen.entity; + +import lombok.Data; + +/** + *

+ * 列属性: https://blog.csdn.net/lkforce/article/details/79557482 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:46 + */ +@Data +public class ColumnEntity { + /** + * 列表 + */ + private String columnName; + /** + * 数据类型 + */ + private String dataType; + /** + * 备注 + */ + private String comments; + /** + * 驼峰属性 + */ + private String caseAttrName; + /** + * 普通属性 + */ + private String lowerAttrName; + /** + * 属性类型 + */ + private String attrType; + /** + * jdbc类型 + */ + private String jdbcType; + /** + * 其他信息 + */ + private String extra; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java new file mode 100644 index 0000000..107d136 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java @@ -0,0 +1,43 @@ +package com.xkcoding.codegen.entity; + +import lombok.Data; + +/** + *

+ * 生成配置 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:47 + */ +@Data +public class GenConfig { + /** + * 请求参数 + */ + private TableRequest request; + /** + * 包名 + */ + private String packageName; + /** + * 作者 + */ + private String author; + /** + * 模块名称 + */ + private String moduleName; + /** + * 表前缀 + */ + private String tablePrefix; + /** + * 表名称 + */ + private String tableName; + /** + * 表备注 + */ + private String comments; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java new file mode 100755 index 0000000..1feb7c2 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java @@ -0,0 +1,41 @@ +package com.xkcoding.codegen.entity; + +import lombok.Data; + +import java.util.List; + +/** + *

+ * 表属性: https://blog.csdn.net/lkforce/article/details/79557482 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:47 + */ +@Data +public class TableEntity { + /** + * 名称 + */ + private String tableName; + /** + * 备注 + */ + private String comments; + /** + * 主键 + */ + private ColumnEntity pk; + /** + * 列名 + */ + private List columns; + /** + * 驼峰类型 + */ + private String caseClassName; + /** + * 普通类型 + */ + private String lowerClassName; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java new file mode 100644 index 0000000..f091c7f --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java @@ -0,0 +1,43 @@ +package com.xkcoding.codegen.entity; + +import lombok.Data; + +/** + *

+ * 表格请求参数 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:24 + */ +@Data +public class TableRequest { + /** + * 当前页 + */ + private Integer currentPage; + /** + * 每页条数 + */ + private Integer pageSize; + /** + * jdbc-前缀 + */ + private String prepend; + /** + * jdbc-url + */ + private String url; + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + /** + * 表名 + */ + private String tableName; +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java b/demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java new file mode 100644 index 0000000..c71af32 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java @@ -0,0 +1,32 @@ +package com.xkcoding.codegen.service; + +import cn.hutool.db.Entity; +import com.xkcoding.codegen.common.PageResult; +import com.xkcoding.codegen.entity.GenConfig; +import com.xkcoding.codegen.entity.TableRequest; + +/** + *

+ * 代码生成器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:15 + */ +public interface CodeGenService { + /** + * 生成代码 + * + * @param genConfig 生成配置 + * @return 代码压缩文件 + */ + byte[] generatorCode(GenConfig genConfig); + + /** + * 分页查询表信息 + * + * @param request 请求参数 + * @return 表名分页信息 + */ + PageResult listTables(TableRequest request); +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java b/demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java new file mode 100755 index 0000000..b39bd19 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java @@ -0,0 +1,130 @@ +package com.xkcoding.codegen.service.impl; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Db; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import com.xkcoding.codegen.common.PageResult; +import com.xkcoding.codegen.entity.GenConfig; +import com.xkcoding.codegen.entity.TableRequest; +import com.xkcoding.codegen.service.CodeGenService; +import com.xkcoding.codegen.utils.CodeGenUtil; +import com.xkcoding.codegen.utils.DbUtil; +import com.zaxxer.hikari.HikariDataSource; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.util.List; +import java.util.zip.ZipOutputStream; + +/** + *

+ * 代码生成器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:15 + */ +@Service +@AllArgsConstructor +public class CodeGenServiceImpl implements CodeGenService { + private final String TABLE_SQL_TEMPLATE = "select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables where table_schema = (select database()) %s order by create_time desc"; + + private final String COLUMN_SQL_TEMPLATE = "select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns where table_name = ? and table_schema = (select database()) order by ordinal_position"; + + private final String COUNT_SQL_TEMPLATE = "select count(1) from (%s)tmp"; + + private final String PAGE_SQL_TEMPLATE = " limit ?,?"; + + /** + * 分页查询表信息 + * + * @param request 请求参数 + * @return 表名分页信息 + */ + @Override + @SneakyThrows + public PageResult listTables(TableRequest request) { + HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); + Db db = new Db(dataSource); + + Page page = new Page(request.getCurrentPage(), request.getPageSize()); + int start = page.getStartPosition(); + int pageSize = page.getPageSize(); + + String paramSql = StrUtil.EMPTY; + if (StrUtil.isNotBlank(request.getTableName())) { + paramSql = "and table_name like concat('%', ?, '%')"; + } + String sql = String.format(TABLE_SQL_TEMPLATE, paramSql); + String countSql = String.format(COUNT_SQL_TEMPLATE, sql); + + List query; + BigDecimal count; + if (StrUtil.isNotBlank(request.getTableName())) { + query = db.query(sql + PAGE_SQL_TEMPLATE, request.getTableName(), start, pageSize); + count = (BigDecimal) db.queryNumber(countSql, request.getTableName()); + } else { + query = db.query(sql + PAGE_SQL_TEMPLATE, start, pageSize); + count = (BigDecimal) db.queryNumber(countSql); + } + + PageResult pageResult = new PageResult<>(count.longValue(), page.getPageNumber(), page.getPageSize(), query); + + dataSource.close(); + return pageResult; + } + + /** + * 生成代码 + * + * @param genConfig 生成配置 + * @return 代码压缩文件 + */ + @Override + public byte[] generatorCode(GenConfig genConfig) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + + //查询表信息 + Entity table = queryTable(genConfig.getRequest()); + //查询列信息 + List columns = queryColumns(genConfig.getRequest()); + //生成代码 + CodeGenUtil.generatorCode(genConfig, table, columns, zip); + IoUtil.close(zip); + return outputStream.toByteArray(); + } + + @SneakyThrows + private Entity queryTable(TableRequest request) { + HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); + Db db = new Db(dataSource); + + String paramSql = StrUtil.EMPTY; + if (StrUtil.isNotBlank(request.getTableName())) { + paramSql = "and table_name = ?"; + } + String sql = String.format(TABLE_SQL_TEMPLATE, paramSql); + Entity entity = db.queryOne(sql, request.getTableName()); + + dataSource.close(); + return entity; + } + + @SneakyThrows + private List queryColumns(TableRequest request) { + HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); + Db db = new Db(dataSource); + + List query = db.query(COLUMN_SQL_TEMPLATE, request.getTableName()); + + dataSource.close(); + return query; + } + +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java b/demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java new file mode 100644 index 0000000..ace9a53 --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java @@ -0,0 +1,264 @@ +package com.xkcoding.codegen.utils; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.setting.dialect.Props; +import com.google.common.collect.Lists; +import com.xkcoding.codegen.constants.GenConstants; +import com.xkcoding.codegen.entity.ColumnEntity; +import com.xkcoding.codegen.entity.GenConfig; +import com.xkcoding.codegen.entity.TableEntity; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.text.WordUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + *

+ * 代码生成器 工具类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 09:27 + */ +@Slf4j +@UtilityClass +public class CodeGenUtil { + + private final String ENTITY_JAVA_VM = "Entity.java.vm"; + private final String MAPPER_JAVA_VM = "Mapper.java.vm"; + private final String SERVICE_JAVA_VM = "Service.java.vm"; + private final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm"; + private final String CONTROLLER_JAVA_VM = "Controller.java.vm"; + private final String MAPPER_XML_VM = "Mapper.xml.vm"; + private final String API_JS_VM = "api.js.vm"; + + private List getTemplates() { + List templates = new ArrayList<>(); + templates.add("template/Entity.java.vm"); + templates.add("template/Mapper.java.vm"); + templates.add("template/Mapper.xml.vm"); + templates.add("template/Service.java.vm"); + templates.add("template/ServiceImpl.java.vm"); + templates.add("template/Controller.java.vm"); + + templates.add("template/api.js.vm"); + return templates; + } + + /** + * 生成代码 + */ + public void generatorCode(GenConfig genConfig, Entity table, List columns, ZipOutputStream zip) { + //配置信息 + Props propsDB2Java = getConfig("generator.properties"); + Props propsDB2Jdbc = getConfig("jdbc_type.properties"); + + boolean hasBigDecimal = false; + //表信息 + TableEntity tableEntity = new TableEntity(); + tableEntity.setTableName(table.getStr("tableName")); + + if (StrUtil.isNotBlank(genConfig.getComments())) { + tableEntity.setComments(genConfig.getComments()); + } else { + tableEntity.setComments(table.getStr("tableComment")); + } + + String tablePrefix; + if (StrUtil.isNotBlank(genConfig.getTablePrefix())) { + tablePrefix = genConfig.getTablePrefix(); + } else { + tablePrefix = propsDB2Java.getStr("tablePrefix"); + } + + //表名转换成Java类名 + String className = tableToJava(tableEntity.getTableName(), tablePrefix); + tableEntity.setCaseClassName(className); + tableEntity.setLowerClassName(StrUtil.lowerFirst(className)); + + //列信息 + List columnList = Lists.newArrayList(); + for (Entity column : columns) { + ColumnEntity columnEntity = new ColumnEntity(); + columnEntity.setColumnName(column.getStr("columnName")); + columnEntity.setDataType(column.getStr("dataType")); + columnEntity.setComments(column.getStr("columnComment")); + columnEntity.setExtra(column.getStr("extra")); + + //列名转换成Java属性名 + String attrName = columnToJava(columnEntity.getColumnName()); + columnEntity.setCaseAttrName(attrName); + columnEntity.setLowerAttrName(StrUtil.lowerFirst(attrName)); + + //列的数据类型,转换成Java类型 + String attrType = propsDB2Java.getStr(columnEntity.getDataType(), "unknownType"); + columnEntity.setAttrType(attrType); + String jdbcType = propsDB2Jdbc.getStr(columnEntity.getDataType(), "unknownType"); + columnEntity.setJdbcType(jdbcType); + if (!hasBigDecimal && "BigDecimal".equals(attrType)) { + hasBigDecimal = true; + } + //是否主键 + if ("PRI".equalsIgnoreCase(column.getStr("columnKey")) && tableEntity.getPk() == null) { + tableEntity.setPk(columnEntity); + } + + columnList.add(columnEntity); + } + tableEntity.setColumns(columnList); + + //没主键,则第一个字段为主键 + if (tableEntity.getPk() == null) { + tableEntity.setPk(tableEntity.getColumns().get(0)); + } + + //设置velocity资源加载器 + Properties prop = new Properties(); + prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + Velocity.init(prop); + //封装模板数据 + Map map = new HashMap<>(16); + map.put("tableName", tableEntity.getTableName()); + map.put("pk", tableEntity.getPk()); + map.put("className", tableEntity.getCaseClassName()); + map.put("classname", tableEntity.getLowerClassName()); + map.put("pathName", tableEntity.getLowerClassName().toLowerCase()); + map.put("columns", tableEntity.getColumns()); + map.put("hasBigDecimal", hasBigDecimal); + map.put("datetime", DateUtil.now()); + map.put("year", DateUtil.year(new Date())); + + if (StrUtil.isNotBlank(genConfig.getComments())) { + map.put("comments", genConfig.getComments()); + } else { + map.put("comments", tableEntity.getComments()); + } + + if (StrUtil.isNotBlank(genConfig.getAuthor())) { + map.put("author", genConfig.getAuthor()); + } else { + map.put("author", propsDB2Java.getStr("author")); + } + + if (StrUtil.isNotBlank(genConfig.getModuleName())) { + map.put("moduleName", genConfig.getModuleName()); + } else { + map.put("moduleName", propsDB2Java.getStr("moduleName")); + } + + if (StrUtil.isNotBlank(genConfig.getPackageName())) { + map.put("package", genConfig.getPackageName()); + map.put("mainPath", genConfig.getPackageName()); + } else { + map.put("package", propsDB2Java.getStr("package")); + map.put("mainPath", propsDB2Java.getStr("mainPath")); + } + VelocityContext context = new VelocityContext(map); + + //获取模板列表 + List templates = getTemplates(); + for (String template : templates) { + //渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8); + tpl.merge(context, sw); + + try { + //添加到zip + zip.putNextEntry(new ZipEntry(Objects.requireNonNull(getFileName(template, tableEntity.getCaseClassName(), map.get("package").toString(), map.get("moduleName").toString())))); + IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString()); + IoUtil.close(sw); + zip.closeEntry(); + } catch (IOException e) { + throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e); + } + } + } + + + /** + * 列名转换成Java属性名 + */ + private String columnToJava(String columnName) { + return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", ""); + } + + /** + * 表名转换成Java类名 + */ + private String tableToJava(String tableName, String tablePrefix) { + if (StrUtil.isNotBlank(tablePrefix)) { + tableName = tableName.replaceFirst(tablePrefix, ""); + } + return columnToJava(tableName); + } + + /** + * 获取配置信息 + */ + private Props getConfig(String fileName) { + Props props = new Props(fileName); + props.autoLoad(true); + return props; + } + + /** + * 获取文件名 + */ + private String getFileName(String template, String className, String packageName, String moduleName) { + // 包路径 + String packagePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; + // 资源路径 + String resourcePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator; + // api路径 + String apiPath = GenConstants.SIGNATURE + File.separator + "api" + File.separator; + + if (StrUtil.isNotBlank(packageName)) { + packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator; + } + + if (template.contains(ENTITY_JAVA_VM)) { + return packagePath + "entity" + File.separator + className + ".java"; + } + + if (template.contains(MAPPER_JAVA_VM)) { + return packagePath + "mapper" + File.separator + className + "Mapper.java"; + } + + if (template.contains(SERVICE_JAVA_VM)) { + return packagePath + "service" + File.separator + className + "Service.java"; + } + + if (template.contains(SERVICE_IMPL_JAVA_VM)) { + return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; + } + + if (template.contains(CONTROLLER_JAVA_VM)) { + return packagePath + "controller" + File.separator + className + "Controller.java"; + } + + if (template.contains(MAPPER_XML_VM)) { + return resourcePath + "mapper" + File.separator + className + "Mapper.xml"; + } + + if (template.contains(API_JS_VM)) { + return apiPath + className.toLowerCase() + ".js"; + } + + return null; + } +} diff --git a/demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java b/demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java new file mode 100644 index 0000000..17503cc --- /dev/null +++ b/demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java @@ -0,0 +1,27 @@ +package com.xkcoding.codegen.utils; + +import com.xkcoding.codegen.entity.TableRequest; +import com.zaxxer.hikari.HikariDataSource; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 数据库工具类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:26 + */ +@Slf4j +@UtilityClass +public class DbUtil { + public HikariDataSource buildFromTableRequest(TableRequest request) { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setJdbcUrl(request.getPrepend() + request.getUrl()); + dataSource.setUsername(request.getUsername()); + dataSource.setPassword(request.getPassword()); + return dataSource; + } + +} diff --git a/spring-boot-demo-codegen/src/main/resources/application.yml b/demo-codegen/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/application.yml rename to demo-codegen/src/main/resources/application.yml diff --git a/spring-boot-demo-codegen/src/main/resources/generator.properties b/demo-codegen/src/main/resources/generator.properties similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/generator.properties rename to demo-codegen/src/main/resources/generator.properties diff --git a/spring-boot-demo-codegen/src/main/resources/jdbc_type.properties b/demo-codegen/src/main/resources/jdbc_type.properties similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/jdbc_type.properties rename to demo-codegen/src/main/resources/jdbc_type.properties diff --git a/demo-codegen/src/main/resources/logback-spring.xml b/demo-codegen/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..dcd48fe --- /dev/null +++ b/demo-codegen/src/main/resources/logback-spring.xml @@ -0,0 +1,79 @@ + + + + + + + INFO + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + + + ERROR + + DENY + + ACCEPT + + + + + + + logs/demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + + + 2MB + + + + + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + + + + Error + + + + + + + logs/demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + 2MB + + + + ${FILE_ERROR_PATTERN} + UTF-8 + + + + + + + + + diff --git a/spring-boot-demo-codegen/src/main/resources/static/index.html b/demo-codegen/src/main/resources/static/index.html similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/static/index.html rename to demo-codegen/src/main/resources/static/index.html diff --git a/spring-boot-demo-codegen/src/main/resources/static/libs/axios/axios.min.js b/demo-codegen/src/main/resources/static/libs/axios/axios.min.js similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/static/libs/axios/axios.min.js rename to demo-codegen/src/main/resources/static/libs/axios/axios.min.js diff --git a/demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js b/demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js new file mode 100644 index 0000000..1fa0daa --- /dev/null +++ b/demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js @@ -0,0 +1,145 @@ +/** + * @version: 1.0 Alpha-1 + * @author Coolite Inc. http://www.coolite.com/ + * @date: 2008-05-13 + * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved. + * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. + * @website: http://www.datejs.com/ + */ +Date.CultureInfo={name:"zh-CN",englishName:"Chinese (People's Republic of China)",nativeName:"中文(中华人民共和国)",dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],abbreviatedDayNames:["日","一","二","三","四","五","六"],shortestDayNames:["日","一","二","三","四","五","六"],firstLetterDayNames:["日","一","二","三","四","五","六"],monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],abbreviatedMonthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],amDesignator:"上午",pmDesignator:"下午",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"ymd",formatPatterns:{shortDate:"yyyy/M/d",longDate:"yyyy'年'M'月'd'日'",shortTime:"H:mm",longTime:"H:mm:ss",fullDateTime:"yyyy'年'M'月'd'日' H:mm:ss",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"M'月'd'日'",yearMonth:"yyyy'年'M'月'"},regexPatterns:{jan:/^一月/i,feb:/^二月/i,mar:/^三月/i,apr:/^四月/i,may:/^五月/i,jun:/^六月/i,jul:/^七月/i,aug:/^八月/i,sep:/^九月/i,oct:/^十月/i,nov:/^十一月/i,dec:/^十二月/i,sun:/^星期日/i,mon:/^星期一/i,tue:/^星期二/i,wed:/^星期三/i,thu:/^星期四/i,fri:/^星期五/i,sat:/^星期六/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]}; +(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;} +return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);} +if(x.seconds){this.addSeconds(x.seconds);} +if(x.minutes){this.addMinutes(x.minutes);} +if(x.hours){this.addHours(x.hours);} +if(x.weeks){this.addWeeks(x.weeks);} +if(x.months){this.addMonths(x.months);} +if(x.years){this.addYears(x.years);} +if(x.days){this.addDays(x.days);} +return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;} +g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;} +$y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(nmax){throw new RangeError(n+" is not a valid value for "+name+".");} +return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());} +if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());} +if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());} +if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());} +if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());} +if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());} +if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());} +if(config.timezone){this.setTimezone(config.timezone);} +if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);} +if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);} +return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;} +else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);} +return this;} +return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;} +return'"'+this.getUTCFullYear()+'-'+ +f(this.getUTCMonth()+1)+'-'+ +f(this.getUTCDate())+'T'+ +f(this.getUTCHours())+':'+ +f(this.getUTCMinutes())+':'+ +f(this.getUTCSeconds())+'Z"';};} +$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}} +var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");} +x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}()); +(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());} +return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;itemp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");} +return this;} +return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;} +return t.addDays(shift);};};for(var i=0;i-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;} +if(k==v){break;}} +return true;} +if(j.substring(j.length-1)!="s"){j+="s";} +return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;i + * ${comments} + *

+ * + * @author ${author} + * @date Created in ${datetime} + */ +@Slf4j +@RestController +@RequestMapping("/${pathName}") +@Api(description = "${className}Controller", tags = {"${comments}"}) +public class ${className}Controller { + @Autowired + private ${className}Service ${classname}Service; + + /** + * 分页查询${comments} + * @param page 分页对象 + * @param ${classname} ${comments} + * @return R + */ + @GetMapping("") + @ApiOperation(value = "分页查询${comments}", notes = "分页查询${comments}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "page", value = "分页参数", required = true), + @ApiImplicitParam(name = "${classname}", value = "查询条件", required = true) + }) + public R list${className}(Page page, ${className} ${classname}) { + return R.success(${classname}Service.page(page,Wrappers.query(${classname}))); + } + + + /** + * 通过id查询${comments} + * @param ${pk.lowerAttrName} id + * @return R + */ + @GetMapping("/{${pk.lowerAttrName}}") + @ApiOperation(value = "通过id查询${comments}", notes = "通过id查询${comments}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) + }) + public R get${className}(@PathVariable("${pk.lowerAttrName}") ${pk.attrType} ${pk.lowerAttrName}){ + return R.success(${classname}Service.getById(${pk.lowerAttrName})); + } + + /** + * 新增${comments} + * @param ${classname} ${comments} + * @return R + */ + @PostMapping + @ApiOperation(value = "新增${comments}", notes = "新增${comments}") + public R save${className}(@RequestBody ${className} ${classname}){ + return R.success(${classname}Service.save(${classname})); + } + + /** + * 修改${comments} + * @param ${pk.lowerAttrName} id + * @param ${classname} ${comments} + * @return R + */ + @PutMapping("/{${pk.lowerAttrName}}") + @ApiOperation(value = "修改${comments}", notes = "修改${comments}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) + }) + public R update${className}(@PathVariable ${pk.attrType} ${pk.lowerAttrName}, @RequestBody ${className} ${classname}){ + return R.success(${classname}Service.updateById(${classname})); + } + + /** + * 通过id删除${comments} + * @param ${pk.lowerAttrName} id + * @return R + */ + @DeleteMapping("/{${pk.lowerAttrName}}") + @ApiOperation(value = "删除${comments}", notes = "删除${comments}") + @ApiImplicitParams({ + @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) + }) + public R delete${className}(@PathVariable ${pk.attrType} ${pk.lowerAttrName}){ + return R.success(${classname}Service.removeById(${pk.lowerAttrName})); + } + +} diff --git a/demo-codegen/src/main/resources/template/Entity.java.vm b/demo-codegen/src/main/resources/template/Entity.java.vm new file mode 100755 index 0000000..fd2c004 --- /dev/null +++ b/demo-codegen/src/main/resources/template/Entity.java.vm @@ -0,0 +1,42 @@ +package ${package}.${moduleName}.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; +import lombok.EqualsAndHashCode; +#if(${hasBigDecimal}) +import java.math.BigDecimal; +#end +import java.time.LocalDateTime; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.NoArgsConstructor; +/** + *

+ * ${comments} + *

+ * + * @author ${author} + * @date Created in ${datetime} + */ +@Data +@NoArgsConstructor +@TableName("${tableName}") +@ApiModel(description = "${comments}") +@EqualsAndHashCode(callSuper = true) +public class ${className} extends Model<${className}> { + private static final long serialVersionUID = 1L; + + #foreach ($column in $columns) + /** + * $column.comments + */ + #if($column.columnName == $pk.columnName) + @TableId + #end + @ApiModelProperty(value = "$column.comments") + private $column.attrType $column.lowerAttrName; + #end + +} diff --git a/demo-codegen/src/main/resources/template/Mapper.java.vm b/demo-codegen/src/main/resources/template/Mapper.java.vm new file mode 100755 index 0000000..f43178f --- /dev/null +++ b/demo-codegen/src/main/resources/template/Mapper.java.vm @@ -0,0 +1,18 @@ +package ${package}.${moduleName}.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.stereotype.Component; +import ${package}.${moduleName}.entity.${className}; + +/** + *

+ * ${comments} + *

+ * + * @author ${author} + * @date Created in ${datetime} + */ +@Component +public interface ${className}Mapper extends BaseMapper<${className}> { + +} diff --git a/spring-boot-demo-codegen/src/main/resources/template/Mapper.xml.vm b/demo-codegen/src/main/resources/template/Mapper.xml.vm similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/template/Mapper.xml.vm rename to demo-codegen/src/main/resources/template/Mapper.xml.vm diff --git a/demo-codegen/src/main/resources/template/Service.java.vm b/demo-codegen/src/main/resources/template/Service.java.vm new file mode 100755 index 0000000..c84c7ec --- /dev/null +++ b/demo-codegen/src/main/resources/template/Service.java.vm @@ -0,0 +1,16 @@ +package ${package}.${moduleName}.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import ${package}.${moduleName}.entity.${className}; + +/** + *

+ * ${comments} + *

+ * + * @author ${author} + * @date Created in ${datetime} + */ +public interface ${className}Service extends IService<${className}> { + +} diff --git a/demo-codegen/src/main/resources/template/ServiceImpl.java.vm b/demo-codegen/src/main/resources/template/ServiceImpl.java.vm new file mode 100755 index 0000000..01a881d --- /dev/null +++ b/demo-codegen/src/main/resources/template/ServiceImpl.java.vm @@ -0,0 +1,21 @@ +package ${package}.${moduleName}.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import ${package}.${moduleName}.entity.${className}; +import ${package}.${moduleName}.mapper.${className}Mapper; +import ${package}.${moduleName}.service.${className}Service; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; +/** + *

+ * ${comments} + *

+ * + * @author ${author} + * @date Created in ${datetime} + */ +@Service +@Slf4j +public class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements ${className}Service { + +} diff --git a/spring-boot-demo-codegen/src/main/resources/template/api.js.vm b/demo-codegen/src/main/resources/template/api.js.vm similarity index 100% rename from spring-boot-demo-codegen/src/main/resources/template/api.js.vm rename to demo-codegen/src/main/resources/template/api.js.vm diff --git a/demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java b/demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java new file mode 100644 index 0000000..e11cb5d --- /dev/null +++ b/demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java @@ -0,0 +1,74 @@ +package com.xkcoding.codegen; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.db.Entity; +import com.xkcoding.codegen.common.PageResult; +import com.xkcoding.codegen.entity.GenConfig; +import com.xkcoding.codegen.entity.TableRequest; +import com.xkcoding.codegen.service.CodeGenService; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; + +/** + *

+ * 代码生成service测试 + *

+ * + * @author yangkai.shen + * @date Created in 2019-03-22 10:34 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class CodeGenServiceTest { + @Autowired + private CodeGenService codeGenService; + + @Test + public void testTablePage() { + TableRequest request = new TableRequest(); + request.setCurrentPage(1); + request.setPageSize(10); + request.setPrepend("jdbc:mysql://"); + request.setUrl("127.0.0.1:3306/spring-boot-demo"); + request.setUsername("root"); + request.setPassword("root"); + request.setTableName("sec_"); + PageResult pageResult = codeGenService.listTables(request); + log.info("【pageResult】= {}", pageResult); + } + + @Test + @SneakyThrows + public void testGeneratorCode() { + GenConfig config = new GenConfig(); + + TableRequest request = new TableRequest(); + request.setPrepend("jdbc:mysql://"); + request.setUrl("127.0.0.1:3306/spring-boot-demo"); + request.setUsername("root"); + request.setPassword("root"); + request.setTableName("shiro_user"); + config.setRequest(request); + + config.setModuleName("shiro"); + config.setAuthor("Yangkai.Shen"); + config.setComments("用户角色信息"); + config.setPackageName("com.xkcoding"); + config.setTablePrefix("shiro_"); + + byte[] zip = codeGenService.generatorCode(config); + OutputStream outputStream = new FileOutputStream(new File("/Users/yangkai.shen/Desktop/" + request.getTableName() + ".zip")); + IoUtil.write(outputStream, true, zip); + } + +} diff --git a/spring-boot-demo-codegen/src/test/java/com/xkcoding/codegen/SpringBootDemoCodegenApplicationTests.java b/demo-codegen/src/test/java/com/xkcoding/codegen/SpringBootDemoCodegenApplicationTests.java similarity index 100% rename from spring-boot-demo-codegen/src/test/java/com/xkcoding/codegen/SpringBootDemoCodegenApplicationTests.java rename to demo-codegen/src/test/java/com/xkcoding/codegen/SpringBootDemoCodegenApplicationTests.java diff --git a/spring-boot-demo-docker/.gitignore b/demo-docker/.gitignore similarity index 100% rename from spring-boot-demo-docker/.gitignore rename to demo-docker/.gitignore diff --git a/spring-boot-demo-docker/Dockerfile b/demo-docker/Dockerfile similarity index 100% rename from spring-boot-demo-docker/Dockerfile rename to demo-docker/Dockerfile diff --git a/spring-boot-demo-docker/README.md b/demo-docker/README.md similarity index 100% rename from spring-boot-demo-docker/README.md rename to demo-docker/README.md diff --git a/demo-docker/pom.xml b/demo-docker/pom.xml new file mode 100644 index 0000000..c489b51 --- /dev/null +++ b/demo-docker/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + demo-docker + 1.0.0-SNAPSHOT + jar + + demo-docker + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.4.9 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-docker + + + org.springframework.boot + spring-boot-maven-plugin + + + com.spotify + dockerfile-maven-plugin + ${dockerfile-version} + + ${project.build.finalName} + ${project.version} + + target/${project.build.finalName}.jar + + + + + + + + + + + + + + + + + diff --git a/demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java b/demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java new file mode 100644 index 0000000..c1a4004 --- /dev/null +++ b/demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.docker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-29 14:59 + */ +@SpringBootApplication +public class SpringBootDemoDockerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDockerApplication.class, args); + } +} diff --git a/demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java b/demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java new file mode 100644 index 0000000..a8b0d39 --- /dev/null +++ b/demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java @@ -0,0 +1,22 @@ +package com.xkcoding.docker.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * Hello Controller + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-29 14:58 + */ +@RestController +@RequestMapping +public class HelloController { + @GetMapping + public String hello() { + return "Hello,From Docker!"; + } +} diff --git a/spring-boot-demo-docker/src/main/resources/application.yml b/demo-docker/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-docker/src/main/resources/application.yml rename to demo-docker/src/main/resources/application.yml diff --git a/spring-boot-demo-docker/src/test/java/com/xkcoding/docker/SpringBootDemoDockerApplicationTests.java b/demo-docker/src/test/java/com/xkcoding/docker/SpringBootDemoDockerApplicationTests.java similarity index 100% rename from spring-boot-demo-docker/src/test/java/com/xkcoding/docker/SpringBootDemoDockerApplicationTests.java rename to demo-docker/src/test/java/com/xkcoding/docker/SpringBootDemoDockerApplicationTests.java diff --git a/spring-boot-demo-dubbo/.gitignore b/demo-dubbo/.gitignore similarity index 100% rename from spring-boot-demo-dubbo/.gitignore rename to demo-dubbo/.gitignore diff --git a/spring-boot-demo-dubbo/README.md b/demo-dubbo/README.md similarity index 100% rename from spring-boot-demo-dubbo/README.md rename to demo-dubbo/README.md diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/.gitignore b/demo-dubbo/dubbo-common/.gitignore similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/.gitignore rename to demo-dubbo/dubbo-common/.gitignore diff --git a/demo-dubbo/dubbo-common/README.md b/demo-dubbo/dubbo-common/README.md new file mode 100644 index 0000000..a44f8f9 --- /dev/null +++ b/demo-dubbo/dubbo-common/README.md @@ -0,0 +1,55 @@ +# spring-boot-demo-dubbo-common + +> 此 module 主要是用于公共部分,主要存放工具类,实体,以及服务提供方/调用方的接口定义 + +## pom.xml + +```xml + + + + spring-boot-demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + spring-boot-demo-dubbo-common + + + UTF-8 + UTF-8 + 1.8 + + + + spring-boot-demo-dubbo-common + + + +``` + +## HelloService.java + +```java +/** + *

+ * Hello服务接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:56 + */ +public interface HelloService { + /** + * 问好 + * + * @param name 姓名 + * @return 问好 + */ + String sayHello(String name); +} +``` + diff --git a/demo-dubbo/dubbo-common/pom.xml b/demo-dubbo/dubbo-common/pom.xml new file mode 100644 index 0000000..c448fdf --- /dev/null +++ b/demo-dubbo/dubbo-common/pom.xml @@ -0,0 +1,24 @@ + + + + demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + dubbo-common + + + UTF-8 + UTF-8 + 1.8 + + + + dubbo-common + + + diff --git a/demo-dubbo/dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java b/demo-dubbo/dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java new file mode 100644 index 0000000..9b52d75 --- /dev/null +++ b/demo-dubbo/dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java @@ -0,0 +1,19 @@ +package com.xkcoding.dubbo.common.service; + +/** + *

+ * Hello服务接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:56 + */ +public interface HelloService { + /** + * 问好 + * + * @param name 姓名 + * @return 问好 + */ + String sayHello(String name); +} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/.gitignore b/demo-dubbo/dubbo-consumer/.gitignore similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/.gitignore rename to demo-dubbo/dubbo-consumer/.gitignore diff --git a/demo-dubbo/dubbo-consumer/README.md b/demo-dubbo/dubbo-consumer/README.md new file mode 100644 index 0000000..6ff76e0 --- /dev/null +++ b/demo-dubbo/dubbo-consumer/README.md @@ -0,0 +1,130 @@ +# spring-boot-demo-dubbo-consumer + +> 此 module 主要是服务调用方的示例 + +## pom.xml + +```xml + + + + spring-boot-demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + spring-boot-demo-dubbo-consumer + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.spring.boot + dubbo-spring-boot-starter + ${dubbo.starter.version} + + + + ${project.groupId} + spring-boot-demo-dubbo-common + ${project.version} + + + + com.101tec + zkclient + ${zkclient.version} + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-dubbo-consumer + + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo + +spring: + dubbo: + application: + name: spring-boot-demo-dubbo-consumer + registry: zookeeper://127.0.0.1:2181 +``` + +## SpringBootDemoDubboConsumerApplication.java + +```java +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:49 + */ +@SpringBootApplication +@EnableDubboConfiguration +public class SpringBootDemoDubboConsumerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDubboConsumerApplication.class, args); + } +} +``` + +## HelloController.java + +```java +/** + *

+ * Hello服务API + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 17:22 + */ +@RestController +@Slf4j +public class HelloController { + @Reference + private HelloService helloService; + + @GetMapping("/sayHello") + public String sayHello(@RequestParam(defaultValue = "xkcoding") String name) { + log.info("i'm ready to call someone......"); + return helloService.sayHello(name); + } +} +``` diff --git a/demo-dubbo/dubbo-consumer/pom.xml b/demo-dubbo/dubbo-consumer/pom.xml new file mode 100644 index 0000000..ed6db99 --- /dev/null +++ b/demo-dubbo/dubbo-consumer/pom.xml @@ -0,0 +1,67 @@ + + + + demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + dubbo-consumer + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.spring.boot + dubbo-spring-boot-starter + ${dubbo.starter.version} + + + + ${project.groupId} + dubbo-common + ${project.version} + + + + com.101tec + zkclient + ${zkclient.version} + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + dubbo-consumer + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java b/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java new file mode 100644 index 0000000..68a5e61 --- /dev/null +++ b/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.dubbo.consumer; + +import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:49 + */ +@SpringBootApplication +@EnableDubboConfiguration +public class SpringBootDemoDubboConsumerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDubboConsumerApplication.class, args); + } +} diff --git a/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java b/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java new file mode 100644 index 0000000..026032e --- /dev/null +++ b/demo-dubbo/dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java @@ -0,0 +1,29 @@ +package com.xkcoding.dubbo.consumer.controller; + +import com.alibaba.dubbo.config.annotation.Reference; +import com.xkcoding.dubbo.common.service.HelloService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * Hello服务API + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 17:22 + */ +@RestController +@Slf4j +public class HelloController { + @Reference + private HelloService helloService; + + @GetMapping("/sayHello") + public String sayHello(@RequestParam(defaultValue = "xkcoding") String name) { + log.info("i'm ready to call someone......"); + return helloService.sayHello(name); + } +} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/resources/application.yml b/demo-dubbo/dubbo-consumer/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/resources/application.yml rename to demo-dubbo/dubbo-consumer/src/main/resources/application.yml diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/test/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplicationTests.java b/demo-dubbo/dubbo-consumer/src/test/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplicationTests.java similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/test/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplicationTests.java rename to demo-dubbo/dubbo-consumer/src/test/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplicationTests.java diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/.gitignore b/demo-dubbo/dubbo-provider/.gitignore similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/.gitignore rename to demo-dubbo/dubbo-provider/.gitignore diff --git a/demo-dubbo/dubbo-provider/README.md b/demo-dubbo/dubbo-provider/README.md new file mode 100644 index 0000000..a6b1774 --- /dev/null +++ b/demo-dubbo/dubbo-provider/README.md @@ -0,0 +1,135 @@ +# spring-boot-demo-dubbo-provider + +> 此 module 主要是服务提供方示例 + +## pom.xml + +```xml + + + + spring-boot-demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + spring-boot-demo-dubbo-provider + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.spring.boot + dubbo-spring-boot-starter + ${dubbo.starter.version} + + + + ${project.groupId} + spring-boot-demo-dubbo-common + ${project.version} + + + + com.101tec + zkclient + ${zkclient.version} + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-dubbo-provider + + + +``` + +## application.yml + +```yaml +server: + port: 9090 + servlet: + context-path: /demo + +spring: + dubbo: + application: + name: spring-boot-demo-dubbo-provider + registry: zookeeper://localhost:2181 +``` + +## SpringBootDemoDubboProviderApplication.java + +```java +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:49 + */ +@EnableDubboConfiguration +@SpringBootApplication +public class SpringBootDemoDubboProviderApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDubboProviderApplication.class, args); + } +} +``` + +## HelloServiceImpl.java + +```java +/** + *

+ * Hello服务实现 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:58 + */ +@Service +@Component +@Slf4j +public class HelloServiceImpl implements HelloService { + /** + * 问好 + * + * @param name 姓名 + * @return 问好 + */ + @Override + public String sayHello(String name) { + log.info("someone is calling me......"); + return "say hello to: " + name; + } +} +``` + diff --git a/demo-dubbo/dubbo-provider/pom.xml b/demo-dubbo/dubbo-provider/pom.xml new file mode 100644 index 0000000..6fdd742 --- /dev/null +++ b/demo-dubbo/dubbo-provider/pom.xml @@ -0,0 +1,67 @@ + + + + demo-dubbo + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + dubbo-provider + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.spring.boot + dubbo-spring-boot-starter + ${dubbo.starter.version} + + + + ${project.groupId} + dubbo-common + ${project.version} + + + + com.101tec + zkclient + ${zkclient.version} + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + dubbo-provider + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java b/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java new file mode 100644 index 0000000..c34b5de --- /dev/null +++ b/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.dubbo.provider; + +import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:49 + */ +@EnableDubboConfiguration +@SpringBootApplication +public class SpringBootDemoDubboProviderApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDubboProviderApplication.class, args); + } +} diff --git a/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java b/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java new file mode 100644 index 0000000..9a69537 --- /dev/null +++ b/demo-dubbo/dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java @@ -0,0 +1,31 @@ +package com.xkcoding.dubbo.provider.service; + +import com.alibaba.dubbo.config.annotation.Service; +import com.xkcoding.dubbo.common.service.HelloService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + *

+ * Hello服务实现 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-25 16:58 + */ +@Service +@Component +@Slf4j +public class HelloServiceImpl implements HelloService { + /** + * 问好 + * + * @param name 姓名 + * @return 问好 + */ + @Override + public String sayHello(String name) { + log.info("someone is calling me......"); + return "say hello to: " + name; + } +} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/resources/application.yml b/demo-dubbo/dubbo-provider/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/resources/application.yml rename to demo-dubbo/dubbo-provider/src/main/resources/application.yml diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/test/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplicationTests.java b/demo-dubbo/dubbo-provider/src/test/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplicationTests.java similarity index 100% rename from spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/test/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplicationTests.java rename to demo-dubbo/dubbo-provider/src/test/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplicationTests.java diff --git a/demo-dubbo/pom.xml b/demo-dubbo/pom.xml new file mode 100644 index 0000000..3d703b3 --- /dev/null +++ b/demo-dubbo/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + demo-dubbo + 1.0.0-SNAPSHOT + + dubbo-common + dubbo-provider + dubbo-consumer + + pom + + demo-dubbo + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.0.0 + 0.10 + + + diff --git a/spring-boot-demo-dynamic-datasource/.gitignore b/demo-dynamic-datasource/.gitignore similarity index 100% rename from spring-boot-demo-dynamic-datasource/.gitignore rename to demo-dynamic-datasource/.gitignore diff --git a/demo-dynamic-datasource/README.md b/demo-dynamic-datasource/README.md new file mode 100644 index 0000000..7c0e2b7 --- /dev/null +++ b/demo-dynamic-datasource/README.md @@ -0,0 +1,669 @@ +# spring-boot-demo-dynamic-datasource + +> 此 demo 主要演示了 Spring Boot 项目如何通过接口`动态添加/删除`数据源,添加数据源之后如何`动态切换`数据源,然后使用 mybatis 查询切换后的数据源的数据。 + +## 1. 环境准备 + +1. 执行 db 目录下的SQL脚本 +2. 在默认数据源下执行 `init.sql` +3. 在所有数据源分别执行 `user.sql` + +## 2. 主要代码 + +### 2.1.pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-dynamic-datasource + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-dynamic-datasource + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + tk.mybatis + mapper-spring-boot-starter + 2.1.5 + + + + mysql + mysql-connector-java + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-dynamic-datasource + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 2.2. 基础配置类 + +- DatasourceConfiguration.java + +> 这个类主要是通过 `DataSourceBuilder` 去构建一个我们自定义的数据源,将其放入 Spring 容器里 + +```java +/** + *

+ * 数据源配置 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 10:27 + */ +@Configuration +public class DatasourceConfiguration { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource dataSource() { + DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); + dataSourceBuilder.type(DynamicDataSource.class); + return dataSourceBuilder.build(); + } +} +``` + +- MybatisConfiguration.java + +> 这个类主要是将我们上一步构建出来的数据源配置到 Mybatis 的 `SqlSessionFactory` 里 + +```java +/** + *

+ * mybatis配置 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:20 + */ +@Configuration +@MapperScan(basePackages = "com.xkcoding.dynamicdatasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory") +public class MybatisConfiguration { + /** + * 创建会话工厂。 + * + * @param dataSource 数据源 + * @return 会话工厂 + */ + @Bean(name = "sqlSessionFactory") + @SneakyThrows + public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(dataSource); + return bean.getObject(); + } +} +``` + +### 2.3. 动态数据源主要逻辑 + +- DatasourceConfigContextHolder.java + +> 该类主要用于绑定当前线程所使用的数据源 id,通过 ThreadLocal 保证同一线程内不可被修改 + +```java +/** + *

+ * 数据源标识管理 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:16 + */ +public class DatasourceConfigContextHolder { + private static final ThreadLocal DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID); + + /** + * 设置默认数据源 + */ + public static void setDefaultDatasource() { + DATASOURCE_HOLDER.remove(); + setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID); + } + + /** + * 获取当前数据源配置id + * + * @return 数据源配置id + */ + public static Long getCurrentDatasourceConfig() { + return DATASOURCE_HOLDER.get(); + } + + /** + * 设置当前数据源配置id + * + * @param id 数据源配置id + */ + public static void setCurrentDatasourceConfig(Long id) { + DATASOURCE_HOLDER.set(id); + } + +} +``` + +- DynamicDataSource.java + +> 该类继承 `com.zaxxer.hikari.HikariDataSource`,主要用于动态切换数据源连接。 + +```java +/** + *

+ * 动态数据源 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 10:41 + */ +@Slf4j +public class DynamicDataSource extends HikariDataSource { + @Override + public Connection getConnection() throws SQLException { + // 获取当前数据源 id + Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig(); + // 根据当前id获取数据源 + HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id); + + if (null == datasource) { + datasource = initDatasource(id); + } + + return datasource.getConnection(); + } + + /** + * 初始化数据源 + * @param id 数据源id + * @return 数据源 + */ + private HikariDataSource initDatasource(Long id) { + HikariDataSource dataSource = new HikariDataSource(); + + // 判断是否是默认数据源 + if (DatasourceHolder.DEFAULT_ID.equals(id)) { + // 默认数据源根据 application.yml 配置的生成 + DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class); + dataSource.setJdbcUrl(properties.getUrl()); + dataSource.setUsername(properties.getUsername()); + dataSource.setPassword(properties.getPassword()); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + } else { + // 不是默认数据源,通过缓存获取对应id的数据源的配置 + DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id); + + if (datasourceConfig == null) { + throw new RuntimeException("无此数据源"); + } + + dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl()); + dataSource.setUsername(datasourceConfig.getUsername()); + dataSource.setPassword(datasourceConfig.getPassword()); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + } + // 将创建的数据源添加到数据源管理器中,绑定当前线程 + DatasourceHolder.INSTANCE.addDatasource(id, dataSource); + return dataSource; + } +} +``` + +- DatasourceScheduler.java + +> 该类主要用于调度任务 + +```java +/** + *

+ * 数据源缓存释放调度器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:42 + */ +public enum DatasourceScheduler { + /** + * 当前实例 + */ + INSTANCE; + + private AtomicInteger cacheTaskNumber = new AtomicInteger(1); + private ScheduledExecutorService scheduler; + + DatasourceScheduler() { + create(); + } + + private void create() { + this.shutdown(); + this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement()))); + } + + private void shutdown() { + if (null != this.scheduler) { + this.scheduler.shutdown(); + } + } + + public void schedule(Runnable task,long delay){ + this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); + } + +} +``` + +- DatasourceManager.java + +> 该类主要用于管理数据源,记录数据源最后使用时间,同时判断是否长时间未使用,超过一定时间未使用,会被释放连接 + +```java +/** + *

+ * 数据源管理类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:27 + */ +public class DatasourceManager { + /** + * 默认释放时间 + */ + private static final Long DEFAULT_RELEASE = 10L; + + /** + * 数据源 + */ + @Getter + private HikariDataSource dataSource; + + /** + * 上一次使用时间 + */ + private LocalDateTime lastUseTime; + + public DatasourceManager(HikariDataSource dataSource) { + this.dataSource = dataSource; + this.lastUseTime = LocalDateTime.now(); + } + + /** + * 是否已过期,如果过期则关闭数据源 + * + * @return 是否过期,{@code true} 过期,{@code false} 未过期 + */ + public boolean isExpired() { + if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) { + return false; + } + this.dataSource.close(); + return true; + } + + /** + * 刷新上次使用时间 + */ + public void refreshTime() { + this.lastUseTime = LocalDateTime.now(); + } +} +``` + +- DatasourceHolder.java + +> 该类主要用于管理数据源,同时通过 `DatasourceScheduler` 定时检查数据源是否长时间未使用,超时则释放连接 + +```java +/** + *

+ * 数据源管理 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:23 + */ +public enum DatasourceHolder { + /** + * 当前实例 + */ + INSTANCE; + + /** + * 启动执行,定时5分钟清理一次 + */ + DatasourceHolder() { + DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000); + } + + /** + * 默认数据源的id + */ + public static final Long DEFAULT_ID = -1L; + + /** + * 管理动态数据源列表。 + */ + private static final Map DATASOURCE_CACHE = new ConcurrentHashMap<>(); + + /** + * 添加动态数据源 + * + * @param id 数据源id + * @param dataSource 数据源 + */ + public synchronized void addDatasource(Long id, HikariDataSource dataSource) { + DatasourceManager datasourceManager = new DatasourceManager(dataSource); + DATASOURCE_CACHE.put(id, datasourceManager); + } + + /** + * 查询动态数据源 + * + * @param id 数据源id + * @return 数据源 + */ + public synchronized HikariDataSource getDatasource(Long id) { + if (DATASOURCE_CACHE.containsKey(id)) { + DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id); + datasourceManager.refreshTime(); + return datasourceManager.getDataSource(); + } + return null; + } + + /** + * 清除超时的数据源 + */ + public synchronized void clearExpiredDatasource() { + DATASOURCE_CACHE.forEach((k, v) -> { + // 排除默认数据源 + if (!DEFAULT_ID.equals(k)) { + if (v.isExpired()) { + DATASOURCE_CACHE.remove(k); + } + } + }); + } + + /** + * 清除动态数据源 + * @param id 数据源id + */ + public synchronized void removeDatasource(Long id) { + if (DATASOURCE_CACHE.containsKey(id)) { + // 关闭数据源 + DATASOURCE_CACHE.get(id).getDataSource().close(); + // 移除缓存 + DATASOURCE_CACHE.remove(id); + } + } +} +``` + +- DatasourceConfigCache.java + +> 该类主要用于缓存数据源的配置,用户生成数据源时,获取数据源连接参数 + +```java +/** + *

+ * 数据源配置缓存 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:13 + */ +public enum DatasourceConfigCache { + /** + * 当前实例 + */ + INSTANCE; + + /** + * 管理动态数据源列表。 + */ + private static final Map CONFIG_CACHE = new ConcurrentHashMap<>(); + + /** + * 添加数据源配置 + * + * @param id 数据源配置id + * @param config 数据源配置 + */ + public synchronized void addConfig(Long id, DatasourceConfig config) { + CONFIG_CACHE.put(id, config); + } + + /** + * 查询数据源配置 + * + * @param id 数据源配置id + * @return 数据源配置 + */ + public synchronized DatasourceConfig getConfig(Long id) { + if (CONFIG_CACHE.containsKey(id)) { + return CONFIG_CACHE.get(id); + } + return null; + } + + /** + * 清除数据源配置 + */ + public synchronized void removeConfig(Long id) { + CONFIG_CACHE.remove(id); + // 同步清除 DatasourceHolder 对应的数据源 + DatasourceHolder.INSTANCE.removeDatasource(id); + } +} +``` + +### 2.4. 启动类 + +> 启动后,使用默认数据源查询数据源配置列表,将其缓存到 `DatasourceConfigCache` 里,以供后续使用 + +```java +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:57 + */ +@SpringBootApplication +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class SpringBootDemoDynamicDatasourceApplication implements CommandLineRunner { + private final DatasourceConfigMapper configMapper; + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDynamicDatasourceApplication.class, args); + } + + @Override + public void run(String... args) { + // 设置默认的数据源 + DatasourceConfigContextHolder.setDefaultDatasource(); + // 查询所有数据库配置列表 + List datasourceConfigs = configMapper.selectAll(); + System.out.println("加载其余数据源配置列表: " + datasourceConfigs); + // 将数据库配置加入缓存 + datasourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config)); + } +} +``` + +### 2.5. 其余代码参考 demo + +## 3. 测试 + +启动项目,可以看到控制台读取到数据库已配置的数据源信息 + +![image-20190905164824155](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062351.png) + +通过 PostMan 等工具测试 + +- 默认数据源查询 + +![image-20190905165240373](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062353.png) + +- 根据数据源id为1的数据源查询 + +![image-20190905165323097](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062354.png) + +- 根据数据源id为2的数据源查询 + +![image-20190905165350355](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062355.png) + +- 可以通过测试数据源的`增加/删除`,再去查询对应数据源的数据 + +> 删除数据源: +> +> - DELETE http://localhost:8080/config/{id} +> +> 新增数据源: +> +> - POST http://localhost:8080/config +> +> - 参数: +> +> ```json +> { +> "host": "数据库IP", +> "port": 3306, +> "username": "用户名", +> "password": "密码", +> "database": "数据库" +> } +> ``` + +## 4. 优化 + +如上测试,我们只需要通过在 header 里传递数据源的参数,即可做到动态切换数据源,怎么做到的呢? + +答案就是 `AOP` + +```java +/** + *

+ * 数据源选择器切面 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:52 + */ +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DatasourceSelectorAspect { + @Pointcut("execution(public * com.xkcoding.dynamic.datasource.controller.*.*(..))") + public void datasourcePointcut() { + } + + /** + * 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源 + */ + @Before("datasourcePointcut()") + public void doBefore(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + // 排除不可切换数据源的方法 + DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class); + if (null != annotation) { + DatasourceConfigContextHolder.setDefaultDatasource(); + } else { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; + HttpServletRequest request = attributes.getRequest(); + String configIdInHeader = request.getHeader("Datasource-Config-Id"); + if (StringUtils.hasText(configIdInHeader)) { + long configId = Long.parseLong(configIdInHeader); + DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId); + } else { + DatasourceConfigContextHolder.setDefaultDatasource(); + } + } + } + + /** + * 后置操作,设置回默认的数据源id + */ + @AfterReturning("datasourcePointcut()") + public void doAfter() { + DatasourceConfigContextHolder.setDefaultDatasource(); + } + +} +``` + +此时需要考虑,我们是否每个方法都允许用户去切换数据源呢?答案肯定是不行的,所以我们定义了一个注解去标识,当前方法仅可以使用默认数据源。 + +```java +/** + *

+ * 用户标识仅可以使用默认数据源 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:37 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DefaultDatasource { +} +``` + +完结,撒花✿✿ヽ(°▽°)ノ✿ diff --git a/spring-boot-demo-dynamic-datasource/db/init.sql b/demo-dynamic-datasource/db/init.sql similarity index 100% rename from spring-boot-demo-dynamic-datasource/db/init.sql rename to demo-dynamic-datasource/db/init.sql diff --git a/spring-boot-demo-dynamic-datasource/db/user.sql b/demo-dynamic-datasource/db/user.sql similarity index 100% rename from spring-boot-demo-dynamic-datasource/db/user.sql rename to demo-dynamic-datasource/db/user.sql diff --git a/demo-dynamic-datasource/pom.xml b/demo-dynamic-datasource/pom.xml new file mode 100644 index 0000000..af81136 --- /dev/null +++ b/demo-dynamic-datasource/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + demo-dynamic-datasource + 1.0.0-SNAPSHOT + jar + + demo-dynamic-datasource + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + tk.mybatis + mapper-spring-boot-starter + 2.1.5 + + + + mysql + mysql-connector-java + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-dynamic-datasource + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java new file mode 100644 index 0000000..ef3b5e3 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java @@ -0,0 +1,42 @@ +package com.xkcoding.dynamic.datasource; + +import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache; +import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder; +import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper; +import com.xkcoding.dynamic.datasource.model.DatasourceConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.List; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:57 + */ +@SpringBootApplication +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class SpringBootDemoDynamicDatasourceApplication implements CommandLineRunner { + private final DatasourceConfigMapper configMapper; + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoDynamicDatasourceApplication.class, args); + } + + @Override + public void run(String... args) { + // 设置默认的数据源 + DatasourceConfigContextHolder.setDefaultDatasource(); + // 查询所有数据库配置列表 + List datasourceConfigs = configMapper.selectAll(); + System.out.println("加载其余数据源配置列表: " + datasourceConfigs); + // 将数据库配置加入缓存 + datasourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config)); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java new file mode 100644 index 0000000..2194e1e --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java @@ -0,0 +1,17 @@ +package com.xkcoding.dynamic.datasource.annotation; + +import java.lang.annotation.*; + +/** + *

+ * 用户标识仅可以使用默认数据源 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:37 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DefaultDatasource { +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java new file mode 100644 index 0000000..909379b --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java @@ -0,0 +1,74 @@ +package com.xkcoding.dynamic.datasource.aspect; + +import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource; +import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; + +/** + *

+ * 数据源选择器切面 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:52 + */ +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DatasourceSelectorAspect { + @Pointcut("execution(public * com.xkcoding.dynamic.datasource.controller.*.*(..))") + public void datasourcePointcut() { + } + + /** + * 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源 + */ + @Before("datasourcePointcut()") + public void doBefore(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + // 排除不可切换数据源的方法 + DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class); + if (null != annotation) { + DatasourceConfigContextHolder.setDefaultDatasource(); + } else { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; + HttpServletRequest request = attributes.getRequest(); + String configIdInHeader = request.getHeader("Datasource-Config-Id"); + if (StringUtils.hasText(configIdInHeader)) { + long configId = Long.parseLong(configIdInHeader); + DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId); + } else { + DatasourceConfigContextHolder.setDefaultDatasource(); + } + } + } + + /** + * 后置操作,设置回默认的数据源id + */ + @AfterReturning("datasourcePointcut()") + public void doAfter() { + DatasourceConfigContextHolder.setDefaultDatasource(); + } + +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java new file mode 100644 index 0000000..b006fa7 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java @@ -0,0 +1,29 @@ +package com.xkcoding.dynamic.datasource.config; + +import com.xkcoding.dynamic.datasource.datasource.DynamicDataSource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** + *

+ * 数据源配置 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 10:27 + */ +@Configuration +public class DatasourceConfiguration { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource dataSource() { + DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); + dataSourceBuilder.type(DynamicDataSource.class); + return dataSourceBuilder.build(); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java new file mode 100644 index 0000000..13700d3 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java @@ -0,0 +1,17 @@ +package com.xkcoding.dynamic.datasource.config; + +import tk.mybatis.mapper.annotation.RegisterMapper; +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.MySqlMapper; + +/** + *

+ * 通用 mapper 自定义 mapper 文件 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:23 + */ +@RegisterMapper +public interface MyMapper extends Mapper, MySqlMapper { +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java new file mode 100644 index 0000000..5ac0b47 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java @@ -0,0 +1,37 @@ +package com.xkcoding.dynamic.datasource.config; + +import lombok.SneakyThrows; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tk.mybatis.spring.annotation.MapperScan; + +import javax.sql.DataSource; + +/** + *

+ * mybatis配置 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:20 + */ +@Configuration +@MapperScan(basePackages = "com.xkcoding.dynamic.datasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory") +public class MybatisConfiguration { + /** + * 创建会话工厂。 + * + * @param dataSource 数据源 + * @return 会话工厂 + */ + @Bean(name = "sqlSessionFactory") + @SneakyThrows + public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(dataSource); + return bean.getObject(); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java new file mode 100644 index 0000000..b6a0cd3 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java @@ -0,0 +1,44 @@ +package com.xkcoding.dynamic.datasource.controller; + +import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource; +import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache; +import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper; +import com.xkcoding.dynamic.datasource.model.DatasourceConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + *

+ * 数据源配置 Controller + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:31 + */ +@RestController +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DatasourceConfigController { + private final DatasourceConfigMapper configMapper; + + /** + * 保存 + */ + @PostMapping("/config") + @DefaultDatasource + public DatasourceConfig insertConfig(@RequestBody DatasourceConfig config) { + configMapper.insertUseGeneratedKeys(config); + DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config); + return config; + } + + /** + * 保存 + */ + @DeleteMapping("/config/{id}") + @DefaultDatasource + public void removeConfig(@PathVariable Long id) { + configMapper.deleteByPrimaryKey(id); + DatasourceConfigCache.INSTANCE.removeConfig(id); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java new file mode 100644 index 0000000..5802bf0 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java @@ -0,0 +1,33 @@ +package com.xkcoding.dynamic.datasource.controller; + +import com.xkcoding.dynamic.datasource.mapper.UserMapper; +import com.xkcoding.dynamic.datasource.model.User; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 用户 Controller + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:40 + */ +@RestController +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class UserController { + private final UserMapper userMapper; + + /** + * 获取用户列表 + */ + @GetMapping("/user") + public List getUserList() { + return userMapper.selectAll(); + } + +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java new file mode 100644 index 0000000..cd834bc --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java @@ -0,0 +1,58 @@ +package com.xkcoding.dynamic.datasource.datasource; + +import com.xkcoding.dynamic.datasource.model.DatasourceConfig; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

+ * 数据源配置缓存 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 17:13 + */ +public enum DatasourceConfigCache { + /** + * 当前实例 + */ + INSTANCE; + + /** + * 管理动态数据源列表。 + */ + private static final Map CONFIG_CACHE = new ConcurrentHashMap<>(); + + /** + * 添加数据源配置 + * + * @param id 数据源配置id + * @param config 数据源配置 + */ + public synchronized void addConfig(Long id, DatasourceConfig config) { + CONFIG_CACHE.put(id, config); + } + + /** + * 查询数据源配置 + * + * @param id 数据源配置id + * @return 数据源配置 + */ + public synchronized DatasourceConfig getConfig(Long id) { + if (CONFIG_CACHE.containsKey(id)) { + return CONFIG_CACHE.get(id); + } + return null; + } + + /** + * 清除数据源配置 + */ + public synchronized void removeConfig(Long id) { + CONFIG_CACHE.remove(id); + // 同步清除 DatasourceHolder 对应的数据源 + DatasourceHolder.INSTANCE.removeDatasource(id); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java new file mode 100644 index 0000000..59db781 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java @@ -0,0 +1,40 @@ +package com.xkcoding.dynamic.datasource.datasource; + +/** + *

+ * 数据源标识管理 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:16 + */ +public class DatasourceConfigContextHolder { + private static final ThreadLocal DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID); + + /** + * 设置默认数据源 + */ + public static void setDefaultDatasource() { + DATASOURCE_HOLDER.remove(); + setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID); + } + + /** + * 获取当前数据源配置id + * + * @return 数据源配置id + */ + public static Long getCurrentDatasourceConfig() { + return DATASOURCE_HOLDER.get(); + } + + /** + * 设置当前数据源配置id + * + * @param id 数据源配置id + */ + public static void setCurrentDatasourceConfig(Long id) { + DATASOURCE_HOLDER.set(id); + } + +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java new file mode 100644 index 0000000..7685a4f --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java @@ -0,0 +1,92 @@ +package com.xkcoding.dynamic.datasource.datasource; + +import com.zaxxer.hikari.HikariDataSource; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

+ * 数据源管理 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:23 + */ +public enum DatasourceHolder { + /** + * 当前实例 + */ + INSTANCE; + + /** + * 启动执行,定时5分钟清理一次 + */ + DatasourceHolder() { + DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000); + } + + /** + * 默认数据源的id + */ + public static final Long DEFAULT_ID = -1L; + + /** + * 管理动态数据源列表。 + */ + private static final Map DATASOURCE_CACHE = new ConcurrentHashMap<>(); + + /** + * 添加动态数据源 + * + * @param id 数据源id + * @param dataSource 数据源 + */ + public synchronized void addDatasource(Long id, HikariDataSource dataSource) { + DatasourceManager datasourceManager = new DatasourceManager(dataSource); + DATASOURCE_CACHE.put(id, datasourceManager); + } + + /** + * 查询动态数据源 + * + * @param id 数据源id + * @return 数据源 + */ + public synchronized HikariDataSource getDatasource(Long id) { + if (DATASOURCE_CACHE.containsKey(id)) { + DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id); + datasourceManager.refreshTime(); + return datasourceManager.getDataSource(); + } + return null; + } + + /** + * 清除超时的数据源 + */ + public synchronized void clearExpiredDatasource() { + DATASOURCE_CACHE.forEach((k, v) -> { + // 排除默认数据源 + if (!DEFAULT_ID.equals(k)) { + if (v.isExpired()) { + DATASOURCE_CACHE.remove(k); + } + } + }); + } + + /** + * 清除动态数据源 + * + * @param id 数据源id + */ + public synchronized void removeDatasource(Long id) { + if (DATASOURCE_CACHE.containsKey(id)) { + // 关闭数据源 + DATASOURCE_CACHE.get(id).getDataSource().close(); + // 移除缓存 + DATASOURCE_CACHE.remove(id); + } + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java new file mode 100644 index 0000000..de46953 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java @@ -0,0 +1,57 @@ +package com.xkcoding.dynamic.datasource.datasource; + +import com.zaxxer.hikari.HikariDataSource; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + *

+ * 数据源管理类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:27 + */ +public class DatasourceManager { + /** + * 默认释放时间 + */ + private static final Long DEFAULT_RELEASE = 10L; + + /** + * 数据源 + */ + @Getter + private HikariDataSource dataSource; + + /** + * 上一次使用时间 + */ + private LocalDateTime lastUseTime; + + public DatasourceManager(HikariDataSource dataSource) { + this.dataSource = dataSource; + this.lastUseTime = LocalDateTime.now(); + } + + /** + * 是否已过期,如果过期则关闭数据源 + * + * @return 是否过期,{@code true} 过期,{@code false} 未过期 + */ + public boolean isExpired() { + if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) { + return false; + } + this.dataSource.close(); + return true; + } + + /** + * 刷新上次使用时间 + */ + public void refreshTime() { + this.lastUseTime = LocalDateTime.now(); + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java new file mode 100644 index 0000000..3832f1a --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java @@ -0,0 +1,44 @@ +package com.xkcoding.dynamic.datasource.datasource; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

+ * 数据源缓存释放调度器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 14:42 + */ +public enum DatasourceScheduler { + /** + * 当前实例 + */ + INSTANCE; + + private AtomicInteger cacheTaskNumber = new AtomicInteger(1); + private ScheduledExecutorService scheduler; + + DatasourceScheduler() { + create(); + } + + private void create() { + this.shutdown(); + this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement()))); + } + + private void shutdown() { + if (null != this.scheduler) { + this.scheduler.shutdown(); + } + } + + public void schedule(Runnable task, long delay) { + this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); + } + +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java new file mode 100644 index 0000000..4a85229 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java @@ -0,0 +1,70 @@ +package com.xkcoding.dynamic.datasource.datasource; + +import com.xkcoding.dynamic.datasource.model.DatasourceConfig; +import com.xkcoding.dynamic.datasource.utils.SpringUtil; +import com.zaxxer.hikari.HikariDataSource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + *

+ * 动态数据源 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 10:41 + */ +@Slf4j +public class DynamicDataSource extends HikariDataSource { + @Override + public Connection getConnection() throws SQLException { + // 获取当前数据源 id + Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig(); + // 根据当前id获取数据源 + HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id); + + if (null == datasource) { + datasource = initDatasource(id); + } + + return datasource.getConnection(); + } + + /** + * 初始化数据源 + * + * @param id 数据源id + * @return 数据源 + */ + private HikariDataSource initDatasource(Long id) { + HikariDataSource dataSource = new HikariDataSource(); + + // 判断是否是默认数据源 + if (DatasourceHolder.DEFAULT_ID.equals(id)) { + // 默认数据源根据 application.yml 配置的生成 + DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class); + dataSource.setJdbcUrl(properties.getUrl()); + dataSource.setUsername(properties.getUsername()); + dataSource.setPassword(properties.getPassword()); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + } else { + // 不是默认数据源,通过缓存获取对应id的数据源的配置 + DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id); + + if (datasourceConfig == null) { + throw new RuntimeException("无此数据源"); + } + + dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl()); + dataSource.setUsername(datasourceConfig.getUsername()); + dataSource.setPassword(datasourceConfig.getPassword()); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + } + // 将创建的数据源添加到数据源管理器中,绑定当前线程 + DatasourceHolder.INSTANCE.addDatasource(id, dataSource); + return dataSource; + } +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java new file mode 100644 index 0000000..a842e53 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java @@ -0,0 +1,17 @@ +package com.xkcoding.dynamic.datasource.mapper; + +import com.xkcoding.dynamic.datasource.config.MyMapper; +import com.xkcoding.dynamic.datasource.model.DatasourceConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 数据源配置 Mapper + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:20 + */ +@Mapper +public interface DatasourceConfigMapper extends MyMapper { +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java new file mode 100644 index 0000000..a0b7b45 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java @@ -0,0 +1,17 @@ +package com.xkcoding.dynamic.datasource.mapper; + +import com.xkcoding.dynamic.datasource.config.MyMapper; +import com.xkcoding.dynamic.datasource.model.User; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 用户 Mapper + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:49 + */ +@Mapper +public interface UserMapper extends MyMapper { +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java new file mode 100644 index 0000000..fc69d45 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java @@ -0,0 +1,69 @@ +package com.xkcoding.dynamic.datasource.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + *

+ * 数据源配置表 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 10:58 + */ +@Data +@Table(name = "datasource_config") +public class DatasourceConfig implements Serializable { + /** + * 主键 + */ + @Id + @Column(name = "`id`") + @GeneratedValue(generator = "JDBC") + private Long id; + + /** + * 数据库地址 + */ + @Column(name = "`host`") + private String host; + + /** + * 数据库端口 + */ + @Column(name = "`port`") + private Integer port; + + /** + * 数据库用户名 + */ + @Column(name = "`username`") + private String username; + + /** + * 数据库密码 + */ + @Column(name = "`password`") + private String password; + + /** + * 数据库名称 + */ + @Column(name = "`database`") + private String database; + + /** + * 构造JDBC URL + * + * @return JDBC URL + */ + public String buildJdbcUrl() { + return String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&useSSL=false", this.host, this.port, this.database); + } + +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java new file mode 100644 index 0000000..e7249d7 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java @@ -0,0 +1,35 @@ +package com.xkcoding.dynamic.datasource.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + *

+ * 用户 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:41 + */ +@Data +@Table(name = "test_user") +public class User implements Serializable { + /** + * 主键 + */ + @Id + @Column(name = "`id`") + @GeneratedValue(generator = "JDBC") + private Long id; + + /** + * 姓名 + */ + @Column(name = "`name`") + private String name; +} diff --git a/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java new file mode 100644 index 0000000..a718417 --- /dev/null +++ b/demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java @@ -0,0 +1,86 @@ +package com.xkcoding.dynamic.datasource.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +/** + *

+ * Spring 工具类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-09-04 16:16 + */ +@Slf4j +@Service +@Lazy(false) +public class SpringUtil implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + + /** + * 取得存储在静态变量中的ApplicationContext. + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 实现ApplicationContextAware接口, 注入Context到静态变量中. + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + SpringUtil.applicationContext = applicationContext; + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) { + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + public static void clearHolder() { + if (log.isDebugEnabled()) { + log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); + } + applicationContext = null; + } + + /** + * 发布事件 + * + * @param event 事件 + */ + public static void publishEvent(ApplicationEvent event) { + if (applicationContext == null) { + return; + } + applicationContext.publishEvent(event); + } + + /** + * 实现DisposableBean接口, 在Context关闭时清理静态变量. + */ + @Override + public void destroy() { + SpringUtil.clearHolder(); + } + +} diff --git a/spring-boot-demo-dynamic-datasource/src/main/resources/application.yml b/demo-dynamic-datasource/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-dynamic-datasource/src/main/resources/application.yml rename to demo-dynamic-datasource/src/main/resources/application.yml diff --git a/spring-boot-demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java b/demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java similarity index 100% rename from spring-boot-demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java rename to demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/.gitignore b/demo-elasticsearch-rest-high-level-client/.gitignore similarity index 100% rename from spring-boot-demo-elasticsearch-rest-high-level-client/.gitignore rename to demo-elasticsearch-rest-high-level-client/.gitignore diff --git a/demo-elasticsearch-rest-high-level-client/README.md b/demo-elasticsearch-rest-high-level-client/README.md new file mode 100644 index 0000000..b0e631d --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/README.md @@ -0,0 +1,493 @@ +# spring-boot-demo-elasticsearch-rest-high-level-client + +> 此 demo 主要演示了 Spring Boot 如何集成 `elasticsearch-rest-high-level-client` 完成对 `ElasticSearch 7.x` 版本的基本 CURD 操作 + +## Elasticsearch 升级 + +先升级到 6.8,索引创建,设置 mapping 等操作加参数:include_type_name=true,然后滚动升级到 7,旧索引可以用 type 访问。具体可以参考: + +https://www.elastic.co/cn/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0 + +https://www.elastic.co/guide/en/elasticsearch/reference/7.0/removal-of-types.html + +## 注意 + +作者编写本 demo 时,ElasticSearch 版本为 `7.3.0`,使用 docker 运行,下面是所有步骤: + +1.下载镜像:`docker pull elasticsearch:7.3.0` + +2.下载安装 `docker-compose`,参考文档: https://docs.docker.com/compose/install/ + +```bash +sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +``` + +3.编写docker-compose 文件 + +```yaml +version: "3" + +services: + es7: + hostname: es7 + container_name: es7 + image: elasticsearch:7.3.0 + volumes: + - "/data/es7/logs:/usr/share/es7/logs:rw" + - "/data/es7/data:/usr/share/es7/data:rw" + restart: on-failure + ports: + - "9200:9200" + - "9300:9300" + environment: + cluster.name: elasticsearch + discovery.type: single-node + logging: + driver: "json-file" + options: + max-size: "50m" + +``` +4.启动: `docker-compose -f elasticsearch.yaml up -d` + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo + com.xkcoding + 1.0.0-SNAPSHOT + + + spring-boot-demo-elasticsearch-rest-high-level-client + spring-boot-demo-elasticsearch-rest-high-level-client + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.hibernate.validator + hibernate-validator + compile + + + + + org.springframework.boot + spring-boot-configuration-processor + + + + + cn.hutool + hutool-all + + + + + org.elasticsearch + elasticsearch + 7.3.0 + + + + + org.elasticsearch.client + elasticsearch-rest-client + 7.3.0 + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.3.0 + + + org.elasticsearch.client + elasticsearch-rest-client + + + org.elasticsearch + elasticsearch + + + + + + + org.projectlombok + lombok + true + + + + + + spring-boot-demo-elasticsearch-rest-high-level-client + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## Person.java + +> 实体类 +> + +```java +package com.xkcoding.elasticsearch.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + * Person + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:04 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Person implements Serializable { + + private static final long serialVersionUID = 8510634155374943623L; + + /** + * 主键 + */ + private Long id; + + /** + * 名字 + */ + private String name; + + /** + * 国家 + */ + private String country; + + /** + * 年龄 + */ + private Integer age; + + /** + * 生日 + */ + private Date birthday; + + /** + * 介绍 + */ + private String remark; + +} +``` + +## PersonService.java + +```java +package com.xkcoding.elasticsearch.service; + +import com.xkcoding.elasticsearch.model.Person; +import org.springframework.lang.Nullable; + +import java.util.List; + +/** + * PersonService + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:07 + */ +public interface PersonService { + + /** + * create Index + * + * @author fxbin + * @param index elasticsearch index name + */ + void createIndex(String index); + + /** + * delete Index + * + * @author fxbin + * @param index elasticsearch index name + */ + void deleteIndex(String index); + + /** + * insert document source + * + * @author fxbin + * @param index elasticsearch index name + * @param list data source + */ + void insert(String index, List list); + + /** + * update document source + * + * @author fxbin + * @param index elasticsearch index name + * @param list data source + */ + void update(String index, List list); + + /** + * delete document source + * + * @author fxbin + * @param person delete data source and allow null object + */ + void delete(String index, @Nullable Person person); + + /** + * search all doc records + * + * @author fxbin + * @param index elasticsearch index name + * @return person list + */ + List searchList(String index); + +} +``` + +## PersonServiceImpl.java + +> service 实现类型,基本CURD操作 + +```java +package com.xkcoding.elasticsearch.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.xkcoding.elasticsearch.model.Person; +import com.xkcoding.elasticsearch.service.base.BaseElasticsearchService; +import com.xkcoding.elasticsearch.service.PersonService; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.SearchHit; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * PersonServiceImpl + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:08 + */ +@Service +public class PersonServiceImpl extends BaseElasticsearchService implements PersonService { + + @Override + public void createIndex(String index) { + createIndexRequest(index); + } + + @Override + public void deleteIndex(String index) { + deleteIndexRequest(index); + } + + @Override + public void insert(String index, List list) { + + try { + list.forEach(person -> { + IndexRequest request = buildIndexRequest(index, String.valueOf(person.getId()), person); + try { + client.index(request, COMMON_OPTIONS); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } finally { + try { + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void update(String index, List list) { + list.forEach(person -> { + updateRequest(index, String.valueOf(person.getId()), person); + }); + } + + @Override + public void delete(String index, Person person) { + if (ObjectUtils.isEmpty(person)) { + // 如果person 对象为空,则删除全量 + searchList(index).forEach(p -> { + deleteRequest(index, String.valueOf(p.getId())); + }); + } + deleteRequest(index, String.valueOf(person.getId())); + } + + @Override + public List searchList(String index) { + SearchResponse searchResponse = search(index); + SearchHit[] hits = searchResponse.getHits().getHits(); + List personList = new ArrayList<>(); + Arrays.stream(hits).forEach(hit -> { + Map sourceAsMap = hit.getSourceAsMap(); + Person person = BeanUtil.mapToBean(sourceAsMap, Person.class, true); + personList.add(person); + }); + return personList; + } +} +``` + + +## ElasticsearchApplicationTests.java + +> 主要功能测试,参见service 注释说明 + +```java +package com.xkcoding.elasticsearch; + +import com.xkcoding.elasticsearch.contants.ElasticsearchConstant; +import com.xkcoding.elasticsearch.model.Person; +import com.xkcoding.elasticsearch.service.PersonService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ElasticsearchApplicationTests { + + @Autowired + private PersonService personService; + + /** + * 测试删除索引 + */ + @Test + public void deleteIndexTest() { + personService.deleteIndex(ElasticsearchConstant.INDEX_NAME); + } + + /** + * 测试创建索引 + */ + @Test + public void createIndexTest() { + personService.createIndex(ElasticsearchConstant.INDEX_NAME); + } + + /** + * 测试新增 + */ + @Test + public void insertTest() { + List list = new ArrayList<>(); + list.add(Person.builder().age(11).birthday(new Date()).country("CN").id(1L).name("哈哈").remark("test1").build()); + list.add(Person.builder().age(22).birthday(new Date()).country("US").id(2L).name("hiahia").remark("test2").build()); + list.add(Person.builder().age(33).birthday(new Date()).country("ID").id(3L).name("呵呵").remark("test3").build()); + + personService.insert(ElasticsearchConstant.INDEX_NAME, list); + } + + /** + * 测试更新 + */ + @Test + public void updateTest() { + Person person = Person.builder().age(33).birthday(new Date()).country("ID_update").id(3L).name("呵呵update").remark("test3_update").build(); + List list = new ArrayList<>(); + list.add(person); + personService.update(ElasticsearchConstant.INDEX_NAME, list); + } + + /** + * 测试删除 + */ + @Test + public void deleteTest() { + personService.delete(ElasticsearchConstant.INDEX_NAME, Person.builder().id(1L).build()); + } + + /** + * 测试查询 + */ + @Test + public void searchListTest() { + List personList = personService.searchList(ElasticsearchConstant.INDEX_NAME); + System.out.println(personList); + } + +} +``` + +## 参考 + +- ElasticSearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html + +- Java High Level REST Client:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.3/java-rest-high.html + diff --git a/demo-elasticsearch-rest-high-level-client/pom.xml b/demo-elasticsearch-rest-high-level-client/pom.xml new file mode 100644 index 0000000..c71eac8 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/pom.xml @@ -0,0 +1,108 @@ + + + 4.0.0 + + spring-boot-demo + com.xkcoding + 1.0.0-SNAPSHOT + + + demo-elasticsearch-rest-high-level-client + demo-elasticsearch-rest-high-level-client + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.hibernate.validator + hibernate-validator + compile + + + + + org.springframework.boot + spring-boot-configuration-processor + + + + + cn.hutool + hutool-all + + + + + org.elasticsearch + elasticsearch + 7.3.0 + + + + + org.elasticsearch.client + elasticsearch-rest-client + 7.3.0 + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.3.0 + + + org.elasticsearch.client + elasticsearch-rest-client + + + org.elasticsearch + elasticsearch + + + + + + + org.projectlombok + lombok + true + + + + + + demo-elasticsearch-rest-high-level-client + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java new file mode 100644 index 0000000..38969ee --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * ElasticsearchApplication + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:10 + */ +@SpringBootApplication +public class ElasticsearchApplication { + + public static void main(String[] args) { + SpringApplication.run(ElasticsearchApplication.class, args); + } + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java new file mode 100644 index 0000000..b0b1225 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java @@ -0,0 +1,80 @@ +package com.xkcoding.elasticsearch.common; + +import lombok.Data; +import org.springframework.lang.Nullable; + +import java.io.Serializable; + +/** + * Result + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:44 + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1696194043024336235L; + + /** + * 错误码 + */ + private int errcode; + + /** + * 错误信息 + */ + private String errmsg; + + /** + * 响应数据 + */ + private T data; + + public Result() { + } + + private Result(ResultCode resultCode) { + this(resultCode.code, resultCode.msg); + } + + private Result(ResultCode resultCode, T data) { + this(resultCode.code, resultCode.msg, data); + } + + private Result(int errcode, String errmsg) { + this(errcode, errmsg, null); + } + + private Result(int errcode, String errmsg, T data) { + this.errcode = errcode; + this.errmsg = errmsg; + this.data = data; + } + + + /** + * 返回成功 + * + * @param 泛型标记 + * @return 响应信息 {@code Result} + */ + public static Result success() { + return new Result<>(ResultCode.SUCCESS); + } + + + /** + * 返回成功-携带数据 + * + * @param data 响应数据 + * @param 泛型标记 + * @return 响应信息 {@code Result} + */ + public static Result success(@Nullable T data) { + return new Result<>(ResultCode.SUCCESS, data); + } + + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java new file mode 100644 index 0000000..87c3359 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java @@ -0,0 +1,31 @@ +package com.xkcoding.elasticsearch.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * ResultCode + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:47 + */ +@Getter +@AllArgsConstructor +public enum ResultCode { + + /** + * 接口调用成功 + */ + SUCCESS(0, "Request Successful"), + + /** + * 服务器暂不可用,建议稍候重试。建议重试次数不超过3次。 + */ + FAILURE(-1, "System Busy"); + + final int code; + + final String msg; + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java new file mode 100644 index 0000000..228b09b --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java @@ -0,0 +1,95 @@ +package com.xkcoding.elasticsearch.config; + +import lombok.RequiredArgsConstructor; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * ElasticsearchAutoConfiguration + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 22:59 + */ +@Configuration +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@EnableConfigurationProperties(ElasticsearchProperties.class) +public class ElasticsearchAutoConfiguration { + + private final ElasticsearchProperties elasticsearchProperties; + + private List httpHosts = new ArrayList<>(); + + @Bean + @ConditionalOnMissingBean + public RestHighLevelClient restHighLevelClient() { + + List clusterNodes = elasticsearchProperties.getClusterNodes(); + clusterNodes.forEach(node -> { + try { + String[] parts = StringUtils.split(node, ":"); + Assert.notNull(parts, "Must defined"); + Assert.state(parts.length == 2, "Must be defined as 'host:port'"); + httpHosts.add(new HttpHost(parts[0], Integer.parseInt(parts[1]), elasticsearchProperties.getSchema())); + } catch (Exception e) { + throw new IllegalStateException("Invalid ES nodes " + "property '" + node + "'", e); + } + }); + RestClientBuilder builder = RestClient.builder(httpHosts.toArray(new HttpHost[0])); + + return getRestHighLevelClient(builder, elasticsearchProperties); + } + + + /** + * get restHistLevelClient + * + * @param builder RestClientBuilder + * @param elasticsearchProperties elasticsearch default properties + * @return {@link org.elasticsearch.client.RestHighLevelClient} + * @author fxbin + */ + private static RestHighLevelClient getRestHighLevelClient(RestClientBuilder builder, ElasticsearchProperties elasticsearchProperties) { + + // Callback used the default {@link RequestConfig} being set to the {@link CloseableHttpClient} + builder.setRequestConfigCallback(requestConfigBuilder -> { + requestConfigBuilder.setConnectTimeout(elasticsearchProperties.getConnectTimeout()); + requestConfigBuilder.setSocketTimeout(elasticsearchProperties.getSocketTimeout()); + requestConfigBuilder.setConnectionRequestTimeout(elasticsearchProperties.getConnectionRequestTimeout()); + return requestConfigBuilder; + }); + + // Callback used to customize the {@link CloseableHttpClient} instance used by a {@link RestClient} instance. + builder.setHttpClientConfigCallback(httpClientBuilder -> { + httpClientBuilder.setMaxConnTotal(elasticsearchProperties.getMaxConnectTotal()); + httpClientBuilder.setMaxConnPerRoute(elasticsearchProperties.getMaxConnectPerRoute()); + return httpClientBuilder; + }); + + // Callback used the basic credential auth + ElasticsearchProperties.Account account = elasticsearchProperties.getAccount(); + if (!StringUtils.isEmpty(account.getUsername()) && !StringUtils.isEmpty(account.getUsername())) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(account.getUsername(), account.getPassword())); + } + return new RestHighLevelClient(builder); + } + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java new file mode 100644 index 0000000..e8a4e15 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java @@ -0,0 +1,116 @@ +package com.xkcoding.elasticsearch.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + +/** + * ElasticsearchProperties + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 22:58 + */ +@Data +@Builder +@Component +@NoArgsConstructor +@AllArgsConstructor +@ConfigurationProperties(prefix = "demo.data.elasticsearch") +public class ElasticsearchProperties { + + /** + * 请求协议 + */ + private String schema = "http"; + + /** + * 集群名称 + */ + private String clusterName = "elasticsearch"; + + /** + * 集群节点 + */ + @NotNull(message = "集群节点不允许为空") + private List clusterNodes = new ArrayList<>(); + + /** + * 连接超时时间(毫秒) + */ + private Integer connectTimeout = 1000; + + /** + * socket 超时时间 + */ + private Integer socketTimeout = 30000; + + /** + * 连接请求超时时间 + */ + private Integer connectionRequestTimeout = 500; + + /** + * 每个路由的最大连接数量 + */ + private Integer maxConnectPerRoute = 10; + + /** + * 最大连接总数量 + */ + private Integer maxConnectTotal = 30; + + /** + * 索引配置信息 + */ + private Index index = new Index(); + + /** + * 认证账户 + */ + private Account account = new Account(); + + /** + * 索引配置信息 + */ + @Data + public static class Index { + + /** + * 分片数量 + */ + private Integer numberOfShards = 3; + + /** + * 副本数量 + */ + private Integer numberOfReplicas = 2; + + } + + /** + * 认证账户 + */ + @Data + public static class Account { + + /** + * 认证用户 + */ + private String username; + + /** + * 认证密码 + */ + private String password; + + } + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java new file mode 100644 index 0000000..b1eb64a --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java @@ -0,0 +1,17 @@ +package com.xkcoding.elasticsearch.contants; + +/** + * ElasticsearchConstant + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:03 + */ +public interface ElasticsearchConstant { + + /** + * 索引名称 + */ + String INDEX_NAME = "person"; + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java new file mode 100644 index 0000000..54d597a --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java @@ -0,0 +1,47 @@ +package com.xkcoding.elasticsearch.exception; + +import com.xkcoding.elasticsearch.common.ResultCode; +import lombok.Getter; + +/** + * ElasticsearchException + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:53 + */ +public class ElasticsearchException extends RuntimeException { + + @Getter + private int errcode; + + @Getter + private String errmsg; + + public ElasticsearchException(ResultCode resultCode) { + this(resultCode.getCode(), resultCode.getMsg()); + } + + public ElasticsearchException(String message) { + super(message); + } + + public ElasticsearchException(Integer errcode, String errmsg) { + super(errmsg); + this.errcode = errcode; + this.errmsg = errmsg; + } + + public ElasticsearchException(String message, Throwable cause) { + super(message, cause); + } + + public ElasticsearchException(Throwable cause) { + super(cause); + } + + public ElasticsearchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java new file mode 100644 index 0000000..4a5cdcf --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java @@ -0,0 +1,56 @@ +package com.xkcoding.elasticsearch.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + * Person + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:04 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Person implements Serializable { + + private static final long serialVersionUID = 8510634155374943623L; + + /** + * 主键 + */ + private Long id; + + /** + * 名字 + */ + private String name; + + /** + * 国家 + */ + private String country; + + /** + * 年龄 + */ + private Integer age; + + /** + * 生日 + */ + private Date birthday; + + /** + * 介绍 + */ + private String remark; + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java new file mode 100644 index 0000000..c35a351 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java @@ -0,0 +1,68 @@ +package com.xkcoding.elasticsearch.service; + +import com.xkcoding.elasticsearch.model.Person; +import org.springframework.lang.Nullable; + +import java.util.List; + +/** + * PersonService + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:07 + */ +public interface PersonService { + + /** + * create Index + * + * @param index elasticsearch index name + * @author fxbin + */ + void createIndex(String index); + + /** + * delete Index + * + * @param index elasticsearch index name + * @author fxbin + */ + void deleteIndex(String index); + + /** + * insert document source + * + * @param index elasticsearch index name + * @param list data source + * @author fxbin + */ + void insert(String index, List list); + + /** + * update document source + * + * @param index elasticsearch index name + * @param list data source + * @author fxbin + */ + void update(String index, List list); + + /** + * delete document source + * + * @param person delete data source and allow null object + * @author fxbin + */ + void delete(String index, @Nullable Person person); + + /** + * search all doc records + * + * @param index elasticsearch index name + * @return person list + * @author fxbin + */ + List searchList(String index); + +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java new file mode 100644 index 0000000..7a0d6b5 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java @@ -0,0 +1,164 @@ +package com.xkcoding.elasticsearch.service.base; + +import cn.hutool.core.bean.BeanUtil; +import com.xkcoding.elasticsearch.config.ElasticsearchProperties; +import com.xkcoding.elasticsearch.exception.ElasticsearchException; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.HttpAsyncResponseConsumerFactory; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.CreateIndexResponse; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import javax.annotation.Resource; +import java.io.IOException; + +/** + * BaseElasticsearchService + * + * @author fxbin + * @version 1.0v + * @since 2019-09-16 15:44 + */ +@Slf4j +public abstract class BaseElasticsearchService { + + @Resource + protected RestHighLevelClient client; + + @Resource + private ElasticsearchProperties elasticsearchProperties; + + protected static final RequestOptions COMMON_OPTIONS; + + static { + RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); + + // 默认缓冲限制为100MB,此处修改为30MB。 + builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024)); + COMMON_OPTIONS = builder.build(); + } + + /** + * create elasticsearch index (asyc) + * + * @param index elasticsearch index + * @author fxbin + */ + protected void createIndexRequest(String index) { + try { + CreateIndexRequest request = new CreateIndexRequest(index); + // Settings for this index + request.settings(Settings.builder().put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards()).put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas())); + + CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS); + + log.info(" whether all of the nodes have acknowledged the request : {}", createIndexResponse.isAcknowledged()); + log.info(" Indicates whether the requisite number of shard copies were started for each shard in the index before timing out :{}", createIndexResponse.isShardsAcknowledged()); + } catch (IOException e) { + throw new ElasticsearchException("创建索引 {" + index + "} 失败"); + } + } + + /** + * delete elasticsearch index + * + * @param index elasticsearch index name + * @author fxbin + */ + protected void deleteIndexRequest(String index) { + DeleteIndexRequest deleteIndexRequest = buildDeleteIndexRequest(index); + try { + client.indices().delete(deleteIndexRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("删除索引 {" + index + "} 失败"); + } + } + + /** + * build DeleteIndexRequest + * + * @param index elasticsearch index name + * @author fxbin + */ + private static DeleteIndexRequest buildDeleteIndexRequest(String index) { + return new DeleteIndexRequest(index); + } + + /** + * build IndexRequest + * + * @param index elasticsearch index name + * @param id request object id + * @param object request object + * @return {@link org.elasticsearch.action.index.IndexRequest} + * @author fxbin + */ + protected static IndexRequest buildIndexRequest(String index, String id, Object object) { + return new IndexRequest(index).id(id).source(BeanUtil.beanToMap(object), XContentType.JSON); + } + + /** + * exec updateRequest + * + * @param index elasticsearch index name + * @param id Document id + * @param object request object + * @author fxbin + */ + protected void updateRequest(String index, String id, Object object) { + try { + UpdateRequest updateRequest = new UpdateRequest(index, id).doc(BeanUtil.beanToMap(object), XContentType.JSON); + client.update(updateRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("更新索引 {" + index + "} 数据 {" + object + "} 失败"); + } + } + + /** + * exec deleteRequest + * + * @param index elasticsearch index name + * @param id Document id + * @author fxbin + */ + protected void deleteRequest(String index, String id) { + try { + DeleteRequest deleteRequest = new DeleteRequest(index, id); + client.delete(deleteRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("删除索引 {" + index + "} 数据id {" + id + "} 失败"); + } + } + + /** + * search all + * + * @param index elasticsearch index name + * @return {@link SearchResponse} + * @author fxbin + */ + protected SearchResponse search(String index) { + SearchRequest searchRequest = new SearchRequest(index); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchRequest.source(searchSourceBuilder); + SearchResponse searchResponse = null; + try { + searchResponse = client.search(searchRequest, COMMON_OPTIONS); + } catch (IOException e) { + e.printStackTrace(); + } + return searchResponse; + } +} diff --git a/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java new file mode 100644 index 0000000..ecbc522 --- /dev/null +++ b/demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java @@ -0,0 +1,90 @@ +package com.xkcoding.elasticsearch.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.xkcoding.elasticsearch.model.Person; +import com.xkcoding.elasticsearch.service.PersonService; +import com.xkcoding.elasticsearch.service.base.BaseElasticsearchService; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.SearchHit; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * PersonServiceImpl + * + * @author fxbin + * @version v1.0 + * @since 2019-09-15 23:08 + */ +@Service +public class PersonServiceImpl extends BaseElasticsearchService implements PersonService { + + @Override + public void createIndex(String index) { + createIndexRequest(index); + } + + @Override + public void deleteIndex(String index) { + deleteIndexRequest(index); + } + + @Override + public void insert(String index, List list) { + + try { + list.forEach(person -> { + IndexRequest request = buildIndexRequest(index, String.valueOf(person.getId()), person); + try { + client.index(request, COMMON_OPTIONS); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } finally { + try { + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void update(String index, List list) { + list.forEach(person -> { + updateRequest(index, String.valueOf(person.getId()), person); + }); + } + + @Override + public void delete(String index, Person person) { + if (ObjectUtils.isEmpty(person)) { + // 如果person 对象为空,则删除全量 + searchList(index).forEach(p -> { + deleteRequest(index, String.valueOf(p.getId())); + }); + } + deleteRequest(index, String.valueOf(person.getId())); + } + + @Override + public List searchList(String index) { + SearchResponse searchResponse = search(index); + SearchHit[] hits = searchResponse.getHits().getHits(); + List personList = new ArrayList<>(); + Arrays.stream(hits).forEach(hit -> { + Map sourceAsMap = hit.getSourceAsMap(); + Person person = BeanUtil.mapToBean(sourceAsMap, Person.class, true); + personList.add(person); + }); + return personList; + } +} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/resources/application.yml b/demo-elasticsearch-rest-high-level-client/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-elasticsearch-rest-high-level-client/src/main/resources/application.yml rename to demo-elasticsearch-rest-high-level-client/src/main/resources/application.yml diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/test/java/com/xkcoding/elasticsearch/ElasticsearchApplicationTests.java b/demo-elasticsearch-rest-high-level-client/src/test/java/com/xkcoding/elasticsearch/ElasticsearchApplicationTests.java similarity index 100% rename from spring-boot-demo-elasticsearch-rest-high-level-client/src/test/java/com/xkcoding/elasticsearch/ElasticsearchApplicationTests.java rename to demo-elasticsearch-rest-high-level-client/src/test/java/com/xkcoding/elasticsearch/ElasticsearchApplicationTests.java diff --git a/spring-boot-demo-elasticsearch/.gitignore b/demo-elasticsearch/.gitignore similarity index 100% rename from spring-boot-demo-elasticsearch/.gitignore rename to demo-elasticsearch/.gitignore diff --git a/demo-elasticsearch/README.md b/demo-elasticsearch/README.md new file mode 100644 index 0000000..67bb956 --- /dev/null +++ b/demo-elasticsearch/README.md @@ -0,0 +1,413 @@ +# spring-boot-demo-elasticsearch + +> 此 demo 主要演示了 Spring Boot 如何集成 `spring-boot-starter-data-elasticsearch` 完成对 ElasticSearch 的高级使用技巧,包括创建索引、配置映射、删除索引、增删改查基本操作、复杂查询、高级查询、聚合查询等。 + +## 注意 + +作者编写本demo时,ElasticSearch版本为 `6.5.3`,使用 docker 运行,下面是所有步骤: + +1. 下载镜像:`docker pull elasticsearch:6.5.3` + +2. 运行容器:`docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch-6.5.3 elasticsearch:6.5.3` + +3. 进入容器:`docker exec -it elasticsearch-6.5.3 /bin/bash` + +4. 安装 ik 分词器:`./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.3/elasticsearch-analysis-ik-6.5.3.zip` + +5. 修改 es 配置文件:`vi ./config/elasticsearch.yml + + ```yaml + cluster.name: "docker-cluster" + network.host: 0.0.0.0 + + # minimum_master_nodes need to be explicitly set when bound on a public IP + # set to 1 to allow single node clusters + # Details: https://github.com/elastic/elasticsearch/pull/17288 + discovery.zen.minimum_master_nodes: 1 + + # just for elasticsearch-head plugin + http.cors.enabled: true + http.cors.allow-origin: "*" + ``` + +6. 退出容器:`exit` + +7. 停止容器:`docker stop elasticsearch-6.5.3` + +8. 启动容器:`docker start elasticsearch-6.5.3` + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-elasticsearch + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-elasticsearch + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + spring-boot-demo-elasticsearch + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## Person.java + +> 实体类 +> +> @Document 注解主要声明索引名、类型名、分片数量和备份数量 +> +> @Field 注解主要声明字段对应ES的类型 + +```java +/** + *

+ * 用户实体类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 17:29 + */ +@Document(indexName = EsConsts.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Person { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名字 + */ + @Field(type = FieldType.Keyword) + private String name; + + /** + * 国家 + */ + @Field(type = FieldType.Keyword) + private String country; + + /** + * 年龄 + */ + @Field(type = FieldType.Integer) + private Integer age; + + /** + * 生日 + */ + @Field(type = FieldType.Date) + private Date birthday; + + /** + * 介绍 + */ + @Field(type = FieldType.Text, analyzer = "ik_smart") + private String remark; +} +``` + +## PersonRepository.java + +```java +/** + *

+ * 用户持久层 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 19:00 + */ +public interface PersonRepository extends ElasticsearchRepository { + + /** + * 根据年龄区间查询 + * + * @param min 最小值 + * @param max 最大值 + * @return 满足条件的用户列表 + */ + List findByAgeBetween(Integer min, Integer max); +} +``` + +## TemplateTest.java + +> 主要测试创建索引、映射配置、删除索引 + +```java +/** + *

+ * 测试 ElasticTemplate 的创建/删除 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 17:46 + */ +public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests { + @Autowired + private ElasticsearchTemplate esTemplate; + + /** + * 测试 ElasticTemplate 创建 index + */ + @Test + public void testCreateIndex() { + // 创建索引,会根据Item类的@Document注解信息来创建 + esTemplate.createIndex(Person.class); + + // 配置映射,会根据Item类中的id、Field等字段来自动完成映射 + esTemplate.putMapping(Person.class); + } + + /** + * 测试 ElasticTemplate 删除 index + */ + @Test + public void testDeleteIndex() { + esTemplate.deleteIndex(Person.class); + } +} +``` + +## PersonRepositoryTest.java + +> 主要功能,参见方法上方注释 + +```java +/** + *

+ * 测试 Repository 操作ES + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 19:03 + */ +@Slf4j +public class PersonRepositoryTest extends SpringBootDemoElasticsearchApplicationTests { + @Autowired + private PersonRepository repo; + + /** + * 测试新增 + */ + @Test + public void save() { + Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。"); + Person save = repo.save(person); + log.info("【save】= {}", save); + } + + /** + * 测试批量新增 + */ + @Test + public void saveList() { + List personList = Lists.newArrayList(); + personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。")); + personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。")); + personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。")); + Iterable people = repo.saveAll(personList); + log.info("【people】= {}", people); + } + + /** + * 测试更新 + */ + @Test + public void update() { + repo.findById(1L).ifPresent(person -> { + person.setRemark(person.getRemark() + "\n更新更新更新更新更新"); + Person save = repo.save(person); + log.info("【save】= {}", save); + }); + } + + /** + * 测试删除 + */ + @Test + public void delete() { + // 主键删除 + repo.deleteById(1L); + + // 对象删除 + repo.findById(2L).ifPresent(person -> repo.delete(person)); + + // 批量删除 + repo.deleteAll(repo.findAll()); + } + + /** + * 测试普通查询,按生日倒序 + */ + @Test + public void select() { + repo.findAll(Sort.by(Sort.Direction.DESC, "birthday")) + .forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday()))); + } + + /** + * 自定义查询,根据年龄范围查询 + */ + @Test + public void customSelectRangeOfAge() { + repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge())); + } + + /** + * 高级查询 + */ + @Test + public void advanceSelect() { + // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装 + MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "孙权"); + log.info("【queryBuilder】= {}", queryBuilder.toString()); + + repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person)); + } + + /** + * 自定义高级查询 + */ + @Test + public void customAdvanceSelect() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 添加基本的分词条件 + queryBuilder.withQuery(QueryBuilders.matchQuery("remark", "东汉")); + // 排序条件 + queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC)); + // 分页条件 + queryBuilder.withPageable(PageRequest.of(0, 2)); + Page people = repo.search(queryBuilder.build()); + log.info("【people】总条数 = {}", people.getTotalElements()); + log.info("【people】总页数 = {}", people.getTotalPages()); + people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge())); + } + + /** + * 测试聚合,测试平均年龄 + */ + @Test + public void agg() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 不查询任何结果 + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); + + // 平均年龄 + queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age")); + + log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); + + AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); + double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue(); + log.info("【avgAge】= {}", avgAge); + } + + /** + * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少 + */ + @Test + public void advanceAgg() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 不查询任何结果 + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); + + // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age + queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country") + // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄 + .subAggregation(AggregationBuilders.avg("avg").field("age"))); + + log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); + + // 3. 查询 + AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); + + // 4. 解析 + // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 + StringTerms country = (StringTerms) people.getAggregation("country"); + // 4.2. 获取桶 + List buckets = country.getBuckets(); + for (StringTerms.Bucket bucket : buckets) { + // 4.3. 获取桶中的key,即国家名称 4.4. 获取桶中的文档数量 + log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount()); + // 4.5. 获取子聚合结果: + InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg"); + log.info("平均年龄:{}", avg); + } + } + +} +``` + +## 参考 + +1. ElasticSearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/6.x/getting-started.html +2. spring-data-elasticsearch 官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.2.RELEASE/reference/html/ + diff --git a/demo-elasticsearch/pom.xml b/demo-elasticsearch/pom.xml new file mode 100644 index 0000000..4b93e31 --- /dev/null +++ b/demo-elasticsearch/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-elasticsearch + 1.0.0-SNAPSHOT + jar + + demo-elasticsearch + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + demo-elasticsearch + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java new file mode 100644 index 0000000..2601284 --- /dev/null +++ b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-27 22:52 + */ +@SpringBootApplication +public class SpringBootDemoElasticsearchApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoElasticsearchApplication.class, args); + } + +} diff --git a/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java new file mode 100644 index 0000000..a67f42f --- /dev/null +++ b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java @@ -0,0 +1,21 @@ +package com.xkcoding.elasticsearch.constants; + +/** + *

+ * ES常量池 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 17:30 + */ +public interface EsConsts { + /** + * 索引名称 + */ + String INDEX_NAME = "person"; + + /** + * 类型名称 + */ + String TYPE_NAME = "person"; +} diff --git a/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java new file mode 100644 index 0000000..1d6f8a6 --- /dev/null +++ b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java @@ -0,0 +1,62 @@ +package com.xkcoding.elasticsearch.model; + +import com.xkcoding.elasticsearch.constants.EsConsts; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.util.Date; + +/** + *

+ * 用户实体类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 17:29 + */ +@Document(indexName = EsConsts.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Person { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名字 + */ + @Field(type = FieldType.Keyword) + private String name; + + /** + * 国家 + */ + @Field(type = FieldType.Keyword) + private String country; + + /** + * 年龄 + */ + @Field(type = FieldType.Integer) + private Integer age; + + /** + * 生日 + */ + @Field(type = FieldType.Date) + private Date birthday; + + /** + * 介绍 + */ + @Field(type = FieldType.Text, analyzer = "ik_smart") + private String remark; +} diff --git a/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java new file mode 100644 index 0000000..49f4475 --- /dev/null +++ b/demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java @@ -0,0 +1,26 @@ +package com.xkcoding.elasticsearch.repository; + +import com.xkcoding.elasticsearch.model.Person; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +/** + *

+ * 用户持久层 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 19:00 + */ +public interface PersonRepository extends ElasticsearchRepository { + + /** + * 根据年龄区间查询 + * + * @param min 最小值 + * @param max 最大值 + * @return 满足条件的用户列表 + */ + List findByAgeBetween(Integer min, Integer max); +} diff --git a/spring-boot-demo-elasticsearch/src/main/resources/application.yml b/demo-elasticsearch/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-elasticsearch/src/main/resources/application.yml rename to demo-elasticsearch/src/main/resources/application.yml diff --git a/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java new file mode 100644 index 0000000..e25bc50 --- /dev/null +++ b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.elasticsearch; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoElasticsearchApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java new file mode 100644 index 0000000..c3b22c4 --- /dev/null +++ b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java @@ -0,0 +1,191 @@ +package com.xkcoding.elasticsearch.repository; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Lists; +import com.xkcoding.elasticsearch.SpringBootDemoElasticsearchApplicationTests; +import com.xkcoding.elasticsearch.model.Person; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; +import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; + +import java.util.List; + +/** + *

+ * 测试 Repository 操作ES + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 19:03 + */ +@Slf4j +public class PersonRepositoryTest extends SpringBootDemoElasticsearchApplicationTests { + @Autowired + private PersonRepository repo; + + /** + * 测试新增 + */ + @Test + public void save() { + Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。"); + Person save = repo.save(person); + log.info("【save】= {}", save); + } + + /** + * 测试批量新增 + */ + @Test + public void saveList() { + List personList = Lists.newArrayList(); + personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。")); + personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。")); + personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。")); + Iterable people = repo.saveAll(personList); + log.info("【people】= {}", people); + } + + /** + * 测试更新 + */ + @Test + public void update() { + repo.findById(1L).ifPresent(person -> { + person.setRemark(person.getRemark() + "\n更新更新更新更新更新"); + Person save = repo.save(person); + log.info("【save】= {}", save); + }); + } + + /** + * 测试删除 + */ + @Test + public void delete() { + // 主键删除 + repo.deleteById(1L); + + // 对象删除 + repo.findById(2L).ifPresent(person -> repo.delete(person)); + + // 批量删除 + repo.deleteAll(repo.findAll()); + } + + /** + * 测试普通查询,按生日倒序 + */ + @Test + public void select() { + repo.findAll(Sort.by(Sort.Direction.DESC, "birthday")).forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday()))); + } + + /** + * 自定义查询,根据年龄范围查询 + */ + @Test + public void customSelectRangeOfAge() { + repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge())); + } + + /** + * 高级查询 + */ + @Test + public void advanceSelect() { + // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装 + MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "孙权"); + log.info("【queryBuilder】= {}", queryBuilder.toString()); + + repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person)); + } + + /** + * 自定义高级查询 + */ + @Test + public void customAdvanceSelect() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 添加基本的分词条件 + queryBuilder.withQuery(QueryBuilders.matchQuery("remark", "东汉")); + // 排序条件 + queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC)); + // 分页条件 + queryBuilder.withPageable(PageRequest.of(0, 2)); + Page people = repo.search(queryBuilder.build()); + log.info("【people】总条数 = {}", people.getTotalElements()); + log.info("【people】总页数 = {}", people.getTotalPages()); + people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge())); + } + + /** + * 测试聚合,测试平均年龄 + */ + @Test + public void agg() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 不查询任何结果 + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); + + // 平均年龄 + queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age")); + + log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); + + AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); + double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue(); + log.info("【avgAge】= {}", avgAge); + } + + /** + * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少 + */ + @Test + public void advanceAgg() { + // 构造查询条件 + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + // 不查询任何结果 + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); + + // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age + queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country") + // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄 + .subAggregation(AggregationBuilders.avg("avg").field("age"))); + + log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); + + // 3. 查询 + AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); + + // 4. 解析 + // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 + StringTerms country = (StringTerms) people.getAggregation("country"); + // 4.2. 获取桶 + List buckets = country.getBuckets(); + for (StringTerms.Bucket bucket : buckets) { + // 4.3. 获取桶中的key,即国家名称 4.4. 获取桶中的文档数量 + log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount()); + // 4.5. 获取子聚合结果: + InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg"); + log.info("平均年龄:{}", avg); + } + } + +} diff --git a/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java new file mode 100644 index 0000000..6d53aa3 --- /dev/null +++ b/demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java @@ -0,0 +1,40 @@ +package com.xkcoding.elasticsearch.template; + +import com.xkcoding.elasticsearch.SpringBootDemoElasticsearchApplicationTests; +import com.xkcoding.elasticsearch.model.Person; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; + +/** + *

+ * 测试 ElasticTemplate 的创建/删除 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-20 17:46 + */ +public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests { + @Autowired + private ElasticsearchTemplate esTemplate; + + /** + * 测试 ElasticTemplate 创建 index + */ + @Test + public void testCreateIndex() { + // 创建索引,会根据Item类的@Document注解信息来创建 + esTemplate.createIndex(Person.class); + + // 配置映射,会根据Item类中的id、Field等字段来自动完成映射 + esTemplate.putMapping(Person.class); + } + + /** + * 测试 ElasticTemplate 删除 index + */ + @Test + public void testDeleteIndex() { + esTemplate.deleteIndex(Person.class); + } +} diff --git a/spring-boot-demo-email/.gitignore b/demo-email/.gitignore similarity index 100% rename from spring-boot-demo-email/.gitignore rename to demo-email/.gitignore diff --git a/demo-email/README.md b/demo-email/README.md new file mode 100644 index 0000000..85d037e --- /dev/null +++ b/demo-email/README.md @@ -0,0 +1,441 @@ +# spring-boot-demo-email + +> 此 demo 主要演示了 Spring Boot 如何整合邮件功能,包括发送简单文本邮件、HTML邮件(包括模板HTML邮件)、附件邮件、静态资源邮件。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-email + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-email + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.1 + + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + spring-boot-demo-email + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + mail: + host: smtp.mxhichina.com + port: 465 + username: spring-boot-demo@xkcoding.com + # 使用 jasypt 加密密码,使用com.xkcoding.email.PasswordTest.testGeneratePassword 生成加密密码,替换 ENC(加密密码) + password: ENC(OT0qGOpXrr1Iog1W+fjOiIDCJdBjHyhy) + protocol: smtp + test-connection: true + default-encoding: UTF-8 + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + mail.smtp.starttls.required: true + mail.smtp.ssl.enable: true + mail.display.sendmail: spring-boot-demo +# 为 jasypt 配置解密秘钥 +jasypt: + encryptor: + password: spring-boot-demo + +``` + +## MailService.java + +```java +/** + *

+ * 邮件接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 11:16 + */ +public interface MailService { + /** + * 发送文本邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + */ + void sendSimpleMail(String to, String subject, String content, String... cc); + + /** + * 发送HTML邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException; + + /** + * 发送带附件的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件地址 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException; + + /** + * 发送正文中有静态资源的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param rscPath 静态资源地址 + * @param rscId 静态资源id + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException; + +} +``` + +## MailServiceImpl.java + +```java +/** + *

+ * 邮件接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 13:49 + */ +@Service +public class MailServiceImpl implements MailService { + @Autowired + private JavaMailSender mailSender; + @Value("${spring.mail.username}") + private String from; + + /** + * 发送文本邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + */ + @Override + public void sendSimpleMail(String to, String subject, String content, String... cc) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(from); + message.setTo(to); + message.setSubject(subject); + message.setText(content); + if (ArrayUtil.isNotEmpty(cc)) { + message.setCc(cc); + } + mailSender.send(message); + } + + /** + * 发送HTML邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + mailSender.send(message); + } + + /** + * 发送带附件的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件地址 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + FileSystemResource file = new FileSystemResource(new File(filePath)); + String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); + helper.addAttachment(fileName, file); + + mailSender.send(message); + } + + /** + * 发送正文中有静态资源的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param rscPath 静态资源地址 + * @param rscId 静态资源id + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + FileSystemResource res = new FileSystemResource(new File(rscPath)); + helper.addInline(rscId, res); + + mailSender.send(message); + } +} +``` + +## MailServiceTest.java + +```java +/** + *

+ * 邮件测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 13:49 + */ +public class MailServiceTest extends SpringBootDemoEmailApplicationTests { + @Autowired + private MailService mailService; + @Autowired + private TemplateEngine templateEngine; + @Autowired + private ApplicationContext context; + + /** + * 测试简单邮件 + */ + @Test + public void sendSimpleMail() { + mailService.sendSimpleMail("237497819@qq.com", "这是一封简单邮件", "这是一封普通的SpringBoot测试邮件"); + } + + /** + * 测试HTML邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendHtmlMail() throws MessagingException { + Context context = new Context(); + context.setVariable("project", "Spring Boot Demo"); + context.setVariable("author", "Yangkai.Shen"); + context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); + + String emailTemplate = templateEngine.process("welcome", context); + mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); + } + + /** + * 测试HTML邮件,自定义模板目录 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendHtmlMail2() throws MessagingException { + + SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); + templateResolver.setApplicationContext(context); + templateResolver.setCacheable(false); + templateResolver.setPrefix("classpath:/email/"); + templateResolver.setSuffix(".html"); + + templateEngine.setTemplateResolver(templateResolver); + + Context context = new Context(); + context.setVariable("project", "Spring Boot Demo"); + context.setVariable("author", "Yangkai.Shen"); + context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); + + String emailTemplate = templateEngine.process("test", context); + mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); + } + + /** + * 测试附件邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendAttachmentsMail() throws MessagingException { + URL resource = ResourceUtil.getResource("static/xkcoding.png"); + mailService.sendAttachmentsMail("237497819@qq.com", "这是一封带附件的邮件", "邮件中有附件,请注意查收!", resource.getPath()); + } + + /** + * 测试静态资源邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendResourceMail() throws MessagingException { + String rscId = "xkcoding"; + String content = "这是带静态资源的邮件
"; + URL resource = ResourceUtil.getResource("static/xkcoding.png"); + mailService.sendResourceMail("237497819@qq.com", "这是一封带静态资源的邮件", content, resource.getPath(), rscId); + } +} +``` + +## welcome.html + +> 此文件为邮件模板,位于 resources/templates 目录下 + +```html + + + + + SpringBootDemo(入门SpringBoot的首选Demo) + + + +
+

欢迎使用 - Powered By

+ + +
+ 如果对你有帮助,请任意打赏 +
+
+
+
+
+
+ +
+
微信打赏
+
+
+
+
+
支付宝打赏
+
+
+
+
+ +
+ + +``` + +## 参考 + +- Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-email +- Spring Boot 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/integration.html#mail diff --git a/demo-email/pom.xml b/demo-email/pom.xml new file mode 100644 index 0000000..2c7b9f4 --- /dev/null +++ b/demo-email/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + demo-email + 1.0.0-SNAPSHOT + jar + + demo-email + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.1 + + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + demo-email + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java b/demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java new file mode 100644 index 0000000..9e054fa --- /dev/null +++ b/demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.email; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-04 22:38 + */ +@SpringBootApplication +public class SpringBootDemoEmailApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoEmailApplication.class, args); + } +} diff --git a/demo-email/src/main/java/com/xkcoding/email/service/MailService.java b/demo-email/src/main/java/com/xkcoding/email/service/MailService.java new file mode 100644 index 0000000..b7e5764 --- /dev/null +++ b/demo-email/src/main/java/com/xkcoding/email/service/MailService.java @@ -0,0 +1,60 @@ +package com.xkcoding.email.service; + +import javax.mail.MessagingException; + +/** + *

+ * 邮件接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 11:16 + */ +public interface MailService { + /** + * 发送文本邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + */ + void sendSimpleMail(String to, String subject, String content, String... cc); + + /** + * 发送HTML邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException; + + /** + * 发送带附件的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件地址 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException; + + /** + * 发送正文中有静态资源的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param rscPath 静态资源地址 + * @param rscId 静态资源id + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException; + +} diff --git a/demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java b/demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java new file mode 100644 index 0000000..59a8e13 --- /dev/null +++ b/demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java @@ -0,0 +1,133 @@ +package com.xkcoding.email.service.impl; + +import cn.hutool.core.util.ArrayUtil; +import com.xkcoding.email.service.MailService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.File; + +/** + *

+ * 邮件接口 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 13:49 + */ +@Service +public class MailServiceImpl implements MailService { + @Autowired + private JavaMailSender mailSender; + @Value("${spring.mail.username}") + private String from; + + /** + * 发送文本邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + */ + @Override + public void sendSimpleMail(String to, String subject, String content, String... cc) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(from); + message.setTo(to); + message.setSubject(subject); + message.setText(content); + if (ArrayUtil.isNotEmpty(cc)) { + message.setCc(cc); + } + mailSender.send(message); + } + + /** + * 发送HTML邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + mailSender.send(message); + } + + /** + * 发送带附件的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param filePath 附件地址 + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + FileSystemResource file = new FileSystemResource(new File(filePath)); + String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); + helper.addAttachment(fileName, file); + + mailSender.send(message); + } + + /** + * 发送正文中有静态资源的邮件 + * + * @param to 收件人地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @param rscPath 静态资源地址 + * @param rscId 静态资源id + * @param cc 抄送地址 + * @throws MessagingException 邮件发送异常 + */ + @Override + public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + if (ArrayUtil.isNotEmpty(cc)) { + helper.setCc(cc); + } + FileSystemResource res = new FileSystemResource(new File(rscPath)); + helper.addInline(rscId, res); + + mailSender.send(message); + } +} diff --git a/spring-boot-demo-email/src/main/resources/application.yml b/demo-email/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-email/src/main/resources/application.yml rename to demo-email/src/main/resources/application.yml diff --git a/spring-boot-demo-email/src/main/resources/email/test.html b/demo-email/src/main/resources/email/test.html similarity index 100% rename from spring-boot-demo-email/src/main/resources/email/test.html rename to demo-email/src/main/resources/email/test.html diff --git a/spring-boot-demo-email/src/main/resources/static/xkcoding.png b/demo-email/src/main/resources/static/xkcoding.png similarity index 100% rename from spring-boot-demo-email/src/main/resources/static/xkcoding.png rename to demo-email/src/main/resources/static/xkcoding.png diff --git a/spring-boot-demo-email/src/main/resources/templates/welcome.html b/demo-email/src/main/resources/templates/welcome.html similarity index 100% rename from spring-boot-demo-email/src/main/resources/templates/welcome.html rename to demo-email/src/main/resources/templates/welcome.html diff --git a/demo-email/src/test/java/com/xkcoding/email/PasswordTest.java b/demo-email/src/test/java/com/xkcoding/email/PasswordTest.java new file mode 100644 index 0000000..3f119e9 --- /dev/null +++ b/demo-email/src/test/java/com/xkcoding/email/PasswordTest.java @@ -0,0 +1,34 @@ +package com.xkcoding.email; + +import org.jasypt.encryption.StringEncryptor; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

+ * 数据库密码测试 + *

+ * + * @author yangkai.shen + * @date Created in 2019-08-27 16:15 + */ +public class PasswordTest extends SpringBootDemoEmailApplicationTests { + @Autowired + private StringEncryptor encryptor; + + /** + * 生成加密密码 + */ + @Test + public void testGeneratePassword() { + // 你的邮箱密码 + String password = "Just4Test!"; + // 加密后的密码(注意:配置上去的时候需要加 ENC(加密密码)) + String encryptPassword = encryptor.encrypt(password); + String decryptPassword = encryptor.decrypt(encryptPassword); + + System.out.println("password = " + password); + System.out.println("encryptPassword = " + encryptPassword); + System.out.println("decryptPassword = " + decryptPassword); + } +} diff --git a/demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java b/demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java new file mode 100644 index 0000000..2b74a39 --- /dev/null +++ b/demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.email; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoEmailApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java b/demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java new file mode 100644 index 0000000..b23d352 --- /dev/null +++ b/demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java @@ -0,0 +1,103 @@ +package com.xkcoding.email.service; + +import cn.hutool.core.io.resource.ResourceUtil; +import com.xkcoding.email.SpringBootDemoEmailApplicationTests; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; + +import javax.mail.MessagingException; +import java.net.URL; + +/** + *

+ * 邮件测试 + *

+ * + * @author yangkai.shen + * @date Created in 2018-11-21 13:49 + */ +public class MailServiceTest extends SpringBootDemoEmailApplicationTests { + @Autowired + private MailService mailService; + @Autowired + private TemplateEngine templateEngine; + @Autowired + private ApplicationContext context; + + /** + * 测试简单邮件 + */ + @Test + public void sendSimpleMail() { + mailService.sendSimpleMail("237497819@qq.com", "这是一封简单邮件", "这是一封普通的SpringBoot测试邮件"); + } + + /** + * 测试HTML邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendHtmlMail() throws MessagingException { + Context context = new Context(); + context.setVariable("project", "Spring Boot Demo"); + context.setVariable("author", "Yangkai.Shen"); + context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); + + String emailTemplate = templateEngine.process("welcome", context); + mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); + } + + /** + * 测试HTML邮件,自定义模板目录 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendHtmlMail2() throws MessagingException { + + SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); + templateResolver.setApplicationContext(context); + templateResolver.setCacheable(false); + templateResolver.setPrefix("classpath:/email/"); + templateResolver.setSuffix(".html"); + + templateEngine.setTemplateResolver(templateResolver); + + Context context = new Context(); + context.setVariable("project", "Spring Boot Demo"); + context.setVariable("author", "Yangkai.Shen"); + context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); + + String emailTemplate = templateEngine.process("test", context); + mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); + } + + /** + * 测试附件邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendAttachmentsMail() throws MessagingException { + URL resource = ResourceUtil.getResource("static/xkcoding.png"); + mailService.sendAttachmentsMail("237497819@qq.com", "这是一封带附件的邮件", "邮件中有附件,请注意查收!", resource.getPath()); + } + + /** + * 测试静态资源邮件 + * + * @throws MessagingException 邮件异常 + */ + @Test + public void sendResourceMail() throws MessagingException { + String rscId = "xkcoding"; + String content = "这是带静态资源的邮件
"; + URL resource = ResourceUtil.getResource("static/xkcoding.png"); + mailService.sendResourceMail("237497819@qq.com", "这是一封带静态资源的邮件", content, resource.getPath(), rscId); + } +} diff --git a/spring-boot-demo-exception-handler/.gitignore b/demo-exception-handler/.gitignore similarity index 100% rename from spring-boot-demo-exception-handler/.gitignore rename to demo-exception-handler/.gitignore diff --git a/demo-exception-handler/README.md b/demo-exception-handler/README.md new file mode 100644 index 0000000..040276f --- /dev/null +++ b/demo-exception-handler/README.md @@ -0,0 +1,260 @@ +# spring-boot-demo-exception-handler + +> 此 demo 演示了如何在Spring Boot中进行统一的异常处理,包括了两种方式的处理:第一种对常见API形式的接口进行异常处理,统一封装返回格式;第二种是对模板页面请求的异常处理,统一处理错误页面。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-exception-handler + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-exception-handler + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-exception-handler + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## ApiResponse.java + +> 统一的API格式返回封装,里面涉及到的 `BaseException` 和`Status` 这两个类,具体代码见 demo。 + +```java +/** + *

+ * 通用的 API 接口封装 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 20:57 + */ +@Data +public class ApiResponse { + /** + * 状态码 + */ + private Integer code; + + /** + * 返回内容 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + /** + * 无参构造函数 + */ + private ApiResponse() { + + } + + /** + * 全参构造函数 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + */ + private ApiResponse(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + /** + * 构造一个自定义的API返回 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse of(Integer code, String message, Object data) { + return new ApiResponse(code, message, data); + } + + /** + * 构造一个成功且带数据的API返回 + * + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofSuccess(Object data) { + return ofStatus(Status.OK, data); + } + + /** + * 构造一个成功且自定义消息的API返回 + * + * @param message 返回内容 + * @return ApiResponse + */ + public static ApiResponse ofMessage(String message) { + return of(Status.OK.getCode(), message, null); + } + + /** + * 构造一个有状态的API返回 + * + * @param status 状态 {@link Status} + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status) { + return ofStatus(status, null); + } + + /** + * 构造一个有状态且带数据的API返回 + * + * @param status 状态 {@link Status} + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status, Object data) { + return of(status.getCode(), status.getMessage(), data); + } + + /** + * 构造一个异常且带数据的API返回 + * + * @param t 异常 + * @param data 返回数据 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t, Object data) { + return of(t.getCode(), t.getMessage(), data); + } + + /** + * 构造一个异常且带数据的API返回 + * + * @param t 异常 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t) { + return ofException(t, null); + } +} +``` + +## DemoExceptionHandler.java + +```java +/** + *

+ * 统一异常处理 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:26 + */ +@ControllerAdvice +@Slf4j +public class DemoExceptionHandler { + private static final String DEFAULT_ERROR_VIEW = "error"; + + /** + * 统一 json 异常处理 + * + * @param exception JsonException + * @return 统一返回 json 格式 + */ + @ExceptionHandler(value = JsonException.class) + @ResponseBody + public ApiResponse jsonErrorHandler(JsonException exception) { + log.error("【JsonException】:{}", exception.getMessage()); + return ApiResponse.ofException(exception); + } + + /** + * 统一 页面 异常处理 + * + * @param exception PageException + * @return 统一跳转到异常页面 + */ + @ExceptionHandler(value = PageException.class) + public ModelAndView pageErrorHandler(PageException exception) { + log.error("【DemoPageException】:{}", exception.getMessage()); + ModelAndView view = new ModelAndView(); + view.addObject("message", exception.getMessage()); + view.setViewName(DEFAULT_ERROR_VIEW); + return view; + } +} +``` + +## error.html + +> 位于 `src/main/resources/template` 目录下 + +```html + + + + + 统一页面异常处理 + + +

统一页面异常处理

+
+ + +``` + diff --git a/demo-exception-handler/pom.xml b/demo-exception-handler/pom.xml new file mode 100644 index 0000000..7e543f1 --- /dev/null +++ b/demo-exception-handler/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + demo-exception-handler + 1.0.0-SNAPSHOT + jar + + demo-exception-handler + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-exception-handler + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java new file mode 100644 index 0000000..68c50c0 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.exception.handler; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 20:49 + */ +@SpringBootApplication +public class SpringBootDemoExceptionHandlerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoExceptionHandlerApplication.class, args); + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java new file mode 100644 index 0000000..3f0eb18 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java @@ -0,0 +1,37 @@ +package com.xkcoding.exception.handler.constant; + +import lombok.Getter; + +/** + *

+ * 状态码封装 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:02 + */ +@Getter +public enum Status { + /** + * 操作成功 + */ + OK(200, "操作成功"), + + /** + * 未知异常 + */ + UNKNOWN_ERROR(500, "服务器出错啦"); + /** + * 状态码 + */ + private Integer code; + /** + * 内容 + */ + private String message; + + Status(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java new file mode 100644 index 0000000..493e93c --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java @@ -0,0 +1,33 @@ +package com.xkcoding.exception.handler.controller; + +import com.xkcoding.exception.handler.constant.Status; +import com.xkcoding.exception.handler.exception.JsonException; +import com.xkcoding.exception.handler.exception.PageException; +import com.xkcoding.exception.handler.model.ApiResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** + *

+ * 测试Controller + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 20:49 + */ +@Controller +public class TestController { + + @GetMapping("/json") + @ResponseBody + public ApiResponse jsonException() { + throw new JsonException(Status.UNKNOWN_ERROR); + } + + @GetMapping("/page") + public ModelAndView pageException() { + throw new PageException(Status.UNKNOWN_ERROR); + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java new file mode 100644 index 0000000..2d42003 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java @@ -0,0 +1,32 @@ +package com.xkcoding.exception.handler.exception; + +import com.xkcoding.exception.handler.constant.Status; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 异常基类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:31 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class BaseException extends RuntimeException { + private Integer code; + private String message; + + public BaseException(Status status) { + super(status.getMessage()); + this.code = status.getCode(); + this.message = status.getMessage(); + } + + public BaseException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java new file mode 100644 index 0000000..fb71770 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java @@ -0,0 +1,24 @@ +package com.xkcoding.exception.handler.exception; + +import com.xkcoding.exception.handler.constant.Status; +import lombok.Getter; + +/** + *

+ * JSON异常 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:18 + */ +@Getter +public class JsonException extends BaseException { + + public JsonException(Status status) { + super(status); + } + + public JsonException(Integer code, String message) { + super(code, message); + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java new file mode 100644 index 0000000..97c9ba7 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java @@ -0,0 +1,24 @@ +package com.xkcoding.exception.handler.exception; + +import com.xkcoding.exception.handler.constant.Status; +import lombok.Getter; + +/** + *

+ * 页面异常 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:18 + */ +@Getter +public class PageException extends BaseException { + + public PageException(Status status) { + super(status); + } + + public PageException(Integer code, String message) { + super(code, message); + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java new file mode 100644 index 0000000..32eacf8 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java @@ -0,0 +1,52 @@ +package com.xkcoding.exception.handler.handler; + +import com.xkcoding.exception.handler.exception.JsonException; +import com.xkcoding.exception.handler.exception.PageException; +import com.xkcoding.exception.handler.model.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** + *

+ * 统一异常处理 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 21:26 + */ +@ControllerAdvice +@Slf4j +public class DemoExceptionHandler { + private static final String DEFAULT_ERROR_VIEW = "error"; + + /** + * 统一 json 异常处理 + * + * @param exception JsonException + * @return 统一返回 json 格式 + */ + @ExceptionHandler(value = JsonException.class) + @ResponseBody + public ApiResponse jsonErrorHandler(JsonException exception) { + log.error("【JsonException】:{}", exception.getMessage()); + return ApiResponse.ofException(exception); + } + + /** + * 统一 页面 异常处理 + * + * @param exception PageException + * @return 统一跳转到异常页面 + */ + @ExceptionHandler(value = PageException.class) + public ModelAndView pageErrorHandler(PageException exception) { + log.error("【DemoPageException】:{}", exception.getMessage()); + ModelAndView view = new ModelAndView(); + view.addObject("message", exception.getMessage()); + view.setViewName(DEFAULT_ERROR_VIEW); + return view; + } +} diff --git a/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java new file mode 100644 index 0000000..8c5fa71 --- /dev/null +++ b/demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java @@ -0,0 +1,127 @@ +package com.xkcoding.exception.handler.model; + +import com.xkcoding.exception.handler.constant.Status; +import com.xkcoding.exception.handler.exception.BaseException; +import lombok.Data; + +/** + *

+ * 通用的 API 接口封装 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-02 20:57 + */ +@Data +public class ApiResponse { + /** + * 状态码 + */ + private Integer code; + + /** + * 返回内容 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + /** + * 无参构造函数 + */ + private ApiResponse() { + + } + + /** + * 全参构造函数 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + */ + private ApiResponse(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + /** + * 构造一个自定义的API返回 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse of(Integer code, String message, Object data) { + return new ApiResponse(code, message, data); + } + + /** + * 构造一个成功且带数据的API返回 + * + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofSuccess(Object data) { + return ofStatus(Status.OK, data); + } + + /** + * 构造一个成功且自定义消息的API返回 + * + * @param message 返回内容 + * @return ApiResponse + */ + public static ApiResponse ofMessage(String message) { + return of(Status.OK.getCode(), message, null); + } + + /** + * 构造一个有状态的API返回 + * + * @param status 状态 {@link Status} + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status) { + return ofStatus(status, null); + } + + /** + * 构造一个有状态且带数据的API返回 + * + * @param status 状态 {@link Status} + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status, Object data) { + return of(status.getCode(), status.getMessage(), data); + } + + /** + * 构造一个异常且带数据的API返回 + * + * @param t 异常 + * @param data 返回数据 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t, Object data) { + return of(t.getCode(), t.getMessage(), data); + } + + /** + * 构造一个异常且带数据的API返回 + * + * @param t 异常 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t) { + return ofException(t, null); + } +} diff --git a/spring-boot-demo-exception-handler/src/main/resources/application.yml b/demo-exception-handler/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-exception-handler/src/main/resources/application.yml rename to demo-exception-handler/src/main/resources/application.yml diff --git a/spring-boot-demo-exception-handler/src/main/resources/templates/error.html b/demo-exception-handler/src/main/resources/templates/error.html similarity index 100% rename from spring-boot-demo-exception-handler/src/main/resources/templates/error.html rename to demo-exception-handler/src/main/resources/templates/error.html diff --git a/demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java b/demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java new file mode 100644 index 0000000..489e1b2 --- /dev/null +++ b/demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.exception.handler; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoExceptionHandlerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-flyway/.gitignore b/demo-flyway/.gitignore similarity index 100% rename from spring-boot-demo-flyway/.gitignore rename to demo-flyway/.gitignore diff --git a/spring-boot-demo-flyway/README.md b/demo-flyway/README.md similarity index 100% rename from spring-boot-demo-flyway/README.md rename to demo-flyway/README.md diff --git a/demo-flyway/pom.xml b/demo-flyway/pom.xml new file mode 100644 index 0000000..37cf701 --- /dev/null +++ b/demo-flyway/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + demo-flyway + 1.0.0-SNAPSHOT + jar + + demo-flyway + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.flywaydb + flyway-core + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + mysql + mysql-connector-java + runtime + + + + + demo-flyway + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java b/demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java new file mode 100644 index 0000000..abc35f9 --- /dev/null +++ b/demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.flyway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2020-03-04 18:30 + */ +@SpringBootApplication +public class SpringBootDemoFlywayApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoFlywayApplication.class, args); + } + +} diff --git a/spring-boot-demo-flyway/src/main/resources/application.yml b/demo-flyway/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-flyway/src/main/resources/application.yml rename to demo-flyway/src/main/resources/application.yml diff --git a/spring-boot-demo-flyway/src/main/resources/db/migration/V1_0__INIT.sql b/demo-flyway/src/main/resources/db/migration/V1_0__INIT.sql similarity index 100% rename from spring-boot-demo-flyway/src/main/resources/db/migration/V1_0__INIT.sql rename to demo-flyway/src/main/resources/db/migration/V1_0__INIT.sql diff --git a/spring-boot-demo-flyway/src/main/resources/db/migration/V1_1__ALTER.sql b/demo-flyway/src/main/resources/db/migration/V1_1__ALTER.sql similarity index 100% rename from spring-boot-demo-flyway/src/main/resources/db/migration/V1_1__ALTER.sql rename to demo-flyway/src/main/resources/db/migration/V1_1__ALTER.sql diff --git a/demo-flyway/src/test/java/com/xkcoding/AppTest.java b/demo-flyway/src/test/java/com/xkcoding/AppTest.java new file mode 100644 index 0000000..a6bfab6 --- /dev/null +++ b/demo-flyway/src/test/java/com/xkcoding/AppTest.java @@ -0,0 +1,18 @@ +package com.xkcoding; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * Unit test for simple App. + */ +public class AppTest { + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() { + assertTrue(true); + } +} diff --git a/spring-boot-demo-graylog/.gitignore b/demo-graylog/.gitignore similarity index 100% rename from spring-boot-demo-graylog/.gitignore rename to demo-graylog/.gitignore diff --git a/spring-boot-demo-graylog/README.md b/demo-graylog/README.md similarity index 100% rename from spring-boot-demo-graylog/README.md rename to demo-graylog/README.md diff --git a/demo-graylog/pom.xml b/demo-graylog/pom.xml new file mode 100644 index 0000000..a27e632 --- /dev/null +++ b/demo-graylog/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + demo-graylog + 1.0.0-SNAPSHOT + jar + + demo-graylog + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + de.siegmar + logback-gelf + 2.0.0 + + + + + demo-graylog + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java b/demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java new file mode 100644 index 0000000..efb57c8 --- /dev/null +++ b/demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.graylog; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-04-23 09:43 + */ +@SpringBootApplication +public class SpringBootDemoGraylogApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoGraylogApplication.class, args); + } + +} diff --git a/spring-boot-demo-graylog/src/main/resources/application.yml b/demo-graylog/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-graylog/src/main/resources/application.yml rename to demo-graylog/src/main/resources/application.yml diff --git a/spring-boot-demo-graylog/src/main/resources/logback-spring.xml b/demo-graylog/src/main/resources/logback-spring.xml similarity index 100% rename from spring-boot-demo-graylog/src/main/resources/logback-spring.xml rename to demo-graylog/src/main/resources/logback-spring.xml diff --git a/spring-boot-demo-graylog/src/test/java/com/xkcoding/graylog/SpringBootDemoGraylogApplicationTests.java b/demo-graylog/src/test/java/com/xkcoding/graylog/SpringBootDemoGraylogApplicationTests.java similarity index 100% rename from spring-boot-demo-graylog/src/test/java/com/xkcoding/graylog/SpringBootDemoGraylogApplicationTests.java rename to demo-graylog/src/test/java/com/xkcoding/graylog/SpringBootDemoGraylogApplicationTests.java diff --git a/spring-boot-demo-helloworld/.gitignore b/demo-helloworld/.gitignore similarity index 100% rename from spring-boot-demo-helloworld/.gitignore rename to demo-helloworld/.gitignore diff --git a/demo-helloworld/README.md b/demo-helloworld/README.md new file mode 100644 index 0000000..d0c5cd8 --- /dev/null +++ b/demo-helloworld/README.md @@ -0,0 +1,111 @@ +# spring-boot-demo-helloworld + +## Runing spring boot demo helloworld + +```sh + $ mvn spring-boot:run +``` +## +> 本 demo 演示如何使用 Spring Boot 写一个hello world + +### pom.xml +```xml + + + 4.0.0 + + spring-boot-demo-helloworld + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-helloworld + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-helloworld + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### SpringBootDemoHelloworldApplication.java + +```java +/** + *

+ * SpringBoot启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-09-28 14:49 + */ +@SpringBootApplication +@RestController +public class SpringBootDemoHelloworldApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoHelloworldApplication.class, args); + } + + /** + * Hello,World + * + * @param who 参数,非必须 + * @return Hello, ${who} + */ + @GetMapping("/hello") + public String sayHello(@RequestParam(required = false, name = "who") String who) { + if (StrUtil.isBlank(who)) { + who = "World"; + } + return StrUtil.format("Hello, {}!", who); + } +} +``` + +### application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +``` + diff --git a/demo-helloworld/pom.xml b/demo-helloworld/pom.xml new file mode 100644 index 0000000..25ec2d9 --- /dev/null +++ b/demo-helloworld/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + demo-helloworld + 1.0.0-SNAPSHOT + jar + + demo-helloworld + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + + demo-helloworld + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java b/demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java new file mode 100644 index 0000000..55b3be6 --- /dev/null +++ b/demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java @@ -0,0 +1,39 @@ +package com.xkcoding.helloworld; + +import cn.hutool.core.util.StrUtil; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * SpringBoot启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-09-28 14:49 + */ +@SpringBootApplication +@RestController +public class SpringBootDemoHelloworldApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoHelloworldApplication.class, args); + } + + /** + * Hello,World + * + * @param who 参数,非必须 + * @return Hello, ${who} + */ + @GetMapping("/hello") + public String sayHello(@RequestParam(required = false, name = "who") String who) { + if (StrUtil.isBlank(who)) { + who = "World"; + } + return StrUtil.format("Hello, {}!", who); + } +} diff --git a/spring-boot-demo-helloworld/src/main/resources/application.yml b/demo-helloworld/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-helloworld/src/main/resources/application.yml rename to demo-helloworld/src/main/resources/application.yml diff --git a/demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java b/demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java new file mode 100644 index 0000000..547e54b --- /dev/null +++ b/demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.helloworld; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoHelloworldApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-https/.gitignore b/demo-https/.gitignore similarity index 100% rename from spring-boot-demo-https/.gitignore rename to demo-https/.gitignore diff --git a/demo-https/README.md b/demo-https/README.md new file mode 100644 index 0000000..042cb6f --- /dev/null +++ b/demo-https/README.md @@ -0,0 +1,110 @@ +# spring-boot-demo-https + +> 此 demo 主要演示了 Spring Boot 如何集成 https + +## 1. 生成证书 + +首先使用 jdk 自带的 keytool 命令生成证书复制到项目的 `resources` 目录下(生成的证书一般在用户目录下 C:\Users\Administrator\server.keystore) + +> 自己生成的证书浏览器会有危险提示,去ssl网站上使用金钱申请则不会 + +![ssl 命令截图](ssl.png) + +## 2. 添加配置 + +1. 在配置文件配置生成的证书 + +```yaml +server: + ssl: + # 证书路径 + key-store: classpath:server.keystore + key-alias: tomcat + enabled: true + key-store-type: JKS + #与申请时输入一致 + key-store-password: 123456 + # 浏览器默认端口 和 80 类似 + port: 443 +``` + +2. 配置 Tomcat + +```java +/** + *

+ * HTTPS 配置类 + *

+ * + * @author yangkai.shen + * @date Created in 2020-01-19 10:31 + */ +@Configuration +public class HttpsConfig { + /** + * 配置 http(80) -> 强制跳转到 https(443) + */ + @Bean + public Connector connector() { + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setScheme("http"); + connector.setPort(80); + connector.setSecure(false); + connector.setRedirectPort(443); + return connector; + } + + @Bean + public TomcatServletWebServerFactory tomcatServletWebServerFactory(Connector connector) { + TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { + @Override + protected void postProcessContext(Context context) { + SecurityConstraint securityConstraint = new SecurityConstraint(); + securityConstraint.setUserConstraint("CONFIDENTIAL"); + SecurityCollection collection = new SecurityCollection(); + collection.addPattern("/*"); + securityConstraint.addCollection(collection); + context.addConstraint(securityConstraint); + } + }; + tomcat.addAdditionalTomcatConnectors(connector); + return tomcat; + } +} +``` + +## 3. 测试 + +启动项目,浏览器访问 http://localhost 将自动跳转到 https://localhost + +## 4. 参考 + +- `keytool`命令参考 + +```bash +$ keytool --help +密钥和证书管理工具 + +命令: + + -certreq 生成证书请求 + -changealias 更改条目的别名 + -delete 删除条目 + -exportcert 导出证书 + -genkeypair 生成密钥对 + -genseckey 生成密钥 + -gencert 根据证书请求生成证书 + -importcert 导入证书或证书链 + -importpass 导入口令 + -importkeystore 从其他密钥库导入一个或所有条目 + -keypasswd 更改条目的密钥口令 + -list 列出密钥库中的条目 + -printcert 打印证书内容 + -printcertreq 打印证书请求的内容 + -printcrl 打印 CRL 文件的内容 + -storepasswd 更改密钥库的存储口令 + +使用 "keytool -command_name -help" 获取 command_name 的用法 +``` + +- [Java Keytool工具简介](https://blog.csdn.net/liumiaocn/article/details/61921014) diff --git a/demo-https/pom.xml b/demo-https/pom.xml new file mode 100644 index 0000000..c603394 --- /dev/null +++ b/demo-https/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + demo-https + 0.0.1-SNAPSHOT + demo-https + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java b/demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java new file mode 100644 index 0000000..2f7dd0a --- /dev/null +++ b/demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.https; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author Chen.Chao + * @date Created in 2020-01-12 10:31 + */ +@SpringBootApplication +public class SpringBootDemoHttpsApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoHttpsApplication.class, args); + } + +} diff --git a/demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java b/demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java new file mode 100644 index 0000000..239227a --- /dev/null +++ b/demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java @@ -0,0 +1,50 @@ +package com.xkcoding.https.config; + +import org.apache.catalina.Context; +import org.apache.catalina.connector.Connector; +import org.apache.tomcat.util.descriptor.web.SecurityCollection; +import org.apache.tomcat.util.descriptor.web.SecurityConstraint; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

+ * HTTPS 配置类 + *

+ * + * @author Chen.Chao + * @date Created in 2020-01-12 10:31 + */ +@Configuration +public class HttpsConfig { + /** + * 配置 http(80) -> 强制跳转到 https(443) + */ + @Bean + public Connector connector() { + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setScheme("http"); + connector.setPort(80); + connector.setSecure(false); + connector.setRedirectPort(443); + return connector; + } + + @Bean + public TomcatServletWebServerFactory tomcatServletWebServerFactory(Connector connector) { + TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { + @Override + protected void postProcessContext(Context context) { + SecurityConstraint securityConstraint = new SecurityConstraint(); + securityConstraint.setUserConstraint("CONFIDENTIAL"); + SecurityCollection collection = new SecurityCollection(); + collection.addPattern("/*"); + securityConstraint.addCollection(collection); + context.addConstraint(securityConstraint); + } + }; + tomcat.addAdditionalTomcatConnectors(connector); + return tomcat; + } +} diff --git a/spring-boot-demo-https/src/main/resources/application.yml b/demo-https/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-https/src/main/resources/application.yml rename to demo-https/src/main/resources/application.yml diff --git a/spring-boot-demo-https/src/main/resources/server.keystore b/demo-https/src/main/resources/server.keystore similarity index 100% rename from spring-boot-demo-https/src/main/resources/server.keystore rename to demo-https/src/main/resources/server.keystore diff --git a/demo-https/src/main/resources/static/index.html b/demo-https/src/main/resources/static/index.html new file mode 100644 index 0000000..933c73e --- /dev/null +++ b/demo-https/src/main/resources/static/index.html @@ -0,0 +1,13 @@ + + + + + spring boot demo https + + +

+ spring boot demo https +

+ + + diff --git a/spring-boot-demo-https/src/test/java/com/xkcoding/https/SpringBootDemoHttpsApplicationTests.java b/demo-https/src/test/java/com/xkcoding/https/SpringBootDemoHttpsApplicationTests.java similarity index 100% rename from spring-boot-demo-https/src/test/java/com/xkcoding/https/SpringBootDemoHttpsApplicationTests.java rename to demo-https/src/test/java/com/xkcoding/https/SpringBootDemoHttpsApplicationTests.java diff --git a/spring-boot-demo-https/ssl.png b/demo-https/ssl.png similarity index 100% rename from spring-boot-demo-https/ssl.png rename to demo-https/ssl.png diff --git a/spring-boot-demo-ldap/.gitignore b/demo-ldap/.gitignore similarity index 100% rename from spring-boot-demo-ldap/.gitignore rename to demo-ldap/.gitignore diff --git a/demo-ldap/README.md b/demo-ldap/README.md new file mode 100644 index 0000000..916df8c --- /dev/null +++ b/demo-ldap/README.md @@ -0,0 +1,393 @@ +# spring-boot-demo-ldap + +> 此 demo 主要演示了 Spring Boot 如何集成 `spring-boot-starter-data-ldap` 完成对 LDAP 的基本 CURD操作, 并给出以登录为实战的 API 示例 + +## docker openldap 安装步骤 + +> 参考: https://github.com/osixia/docker-openldap +1. 下载镜像: `docker pull osixia/openldap:1.2.5` + +2. 运行容器: `docker run -p 389:389 -p 636:636 --name my-openldap --detach osixia/openldap:1.2.5` + +3. 添加管理员: `docker exec my-openldap ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin` + +4. 停止容器:`docker stop my-openldap` + +5. 启动容器:`docker start my-openldap` + + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-ldap + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-ldap + Demo project for Spring Boot + + + spring-boot-demo + com.xkcoding + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.projectlombok + lombok + true + provided + + + + +``` + +## application.yml + +```yaml +spring: + ldap: + urls: ldap://localhost:389 + base: dc=example,dc=org + username: cn=admin,dc=example,dc=org + password: admin +``` + +## Person.java + +> 实体类 +> @Entry 注解 映射ldap对象关系 +```java +/** + * People + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 0:51 + */ +@Data +@Entry( + base = "ou=people", + objectClasses = {"posixAccount", "inetOrgPerson", "top"} +) +public class Person implements Serializable { + + private static final long serialVersionUID = -7946768337975852352L; + + @Id + private Name id; + + private String uidNumber; + + private String gidNumber; + + /** + * 用户名 + */ + @DnAttribute(value = "uid", index = 1) + private String uid; + + /** + * 姓名 + */ + @Attribute(name = "cn") + private String personName; + + /** + * 密码 + */ + private String userPassword; + + /** + * 名字 + */ + private String givenName; + + /** + * 姓氏 + */ + @Attribute(name = "sn") + private String surname; + + /** + * 邮箱 + */ + private String mail; + + /** + * 职位 + */ + private String title; + + /** + * 根目录 + */ + private String homeDirectory; + + /** + * loginShell + */ + private String loginShell; +} +``` + +## PersonRepository.java +> person 数据持久层 +```java +/** + * PersonRepository + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:02 + */ +@Repository +public interface PersonRepository extends CrudRepository { + + /** + * 根据用户名查找 + * + * @param uid 用户名 + * @return com.xkcoding.ldap.entity.Person + */ + Person findByUid(String uid); +} +``` + +## PersonService.java +> 数据操作服务 +```java +/** + * PersonService + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:05 + */ +public interface PersonService { + + /** + * 登录 + * + * @param request {@link LoginRequest} + * @return {@link Result} + */ + Result login(LoginRequest request); + + /** + * 查询全部 + * + * @return {@link Result} + */ + Result listAllPerson(); + + /** + * 保存 + * + * @param person {@link Person} + */ + void save(Person person); + + /** + * 删除 + * + * @param person {@link Person} + */ + void delete(Person person); + +} +``` + +## PersonServiceImpl.java +> person数据操作服务具体逻辑实现类 + +```java +/** + * PersonServiceImpl + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:05 + */ +@Slf4j +@Service +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class PersonServiceImpl implements PersonService { + private final PersonRepository personRepository; + + /** + * 登录 + * + * @param request {@link LoginRequest} + * @return {@link Result} + */ + @Override + public Result login(LoginRequest request) { + log.info("IN LDAP auth"); + + Person user = personRepository.findByUid(request.getUsername()); + + try { + if (ObjectUtils.isEmpty(user)) { + throw new ServiceException("用户名或密码错误,请重新尝试"); + } else { + user.setUserPassword(LdapUtils.asciiToString(user.getUserPassword())); + if (!LdapUtils.verify(user.getUserPassword(), request.getPassword())) { + throw new ServiceException("用户名或密码错误,请重新尝试"); + } + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + log.info("user info:{}", user); + return Result.success(user); + } + + /** + * 查询全部 + * + * @return {@link Result} + */ + @Override + public Result listAllPerson() { + Iterable personList = personRepository.findAll(); + personList.forEach(person -> person.setUserPassword(LdapUtils.asciiToString(person.getUserPassword()))); + return Result.success(personList); + } + + /** + * 保存 + * + * @param person {@link Person} + */ + @Override + public void save(Person person) { + Person p = personRepository.save(person); + log.info("用户{}保存成功", p.getUid()); + } + + /** + * 删除 + * + * @param person {@link Person} + */ + @Override + public void delete(Person person) { + personRepository.delete(person); + log.info("删除用户{}成功", person.getUid()); + } + +} +``` + +## LdapDemoApplicationTests.java +> 测试 +```java +/** + * LdapDemoApplicationTest + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:06 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class LdapDemoApplicationTests { + + @Resource + private PersonService personService; + + @Test + public void contextLoads() { + } + + /** + * 测试查询单个 + */ + @Test + public void loginTest() { + LoginRequest loginRequest = LoginRequest.builder().username("wangwu").password("123456").build(); + Result login = personService.login(loginRequest); + System.out.println(login); + } + + /** + * 测试查询列表 + */ + @Test + public void listAllPersonTest() { + Result result = personService.listAllPerson(); + System.out.println(result); + } + + /** + * 测试保存 + */ + @Test + public void saveTest() { + Person person = new Person(); + + person.setUid("zhaosi"); + + person.setSurname("赵"); + person.setGivenName("四"); + person.setUserPassword("123456"); + + // required field + person.setPersonName("赵四"); + person.setUidNumber("666"); + person.setGidNumber("666"); + person.setHomeDirectory("/home/zhaosi"); + person.setLoginShell("/bin/bash"); + + personService.save(person); + } + + /** + * 测试删除 + */ + @Test + public void deleteTest() { + Person person = new Person(); + person.setUid("zhaosi"); + + personService.delete(person); + } + +} +``` + +## 其余代码参见本 demo + +## 参考 + +spring-data-ldap 官方文档: https://docs.spring.io/spring-data/ldap/docs/2.1.10.RELEASE/reference/html/ diff --git a/demo-ldap/pom.xml b/demo-ldap/pom.xml new file mode 100644 index 0000000..d8134d8 --- /dev/null +++ b/demo-ldap/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + demo-ldap + 1.0.0-SNAPSHOT + jar + + demo-ldap + Demo project for Spring Boot + + + spring-boot-demo + com.xkcoding + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.projectlombok + lombok + true + provided + + + + diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java b/demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java new file mode 100644 index 0000000..862f075 --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java @@ -0,0 +1,19 @@ +package com.xkcoding.ldap; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * LdapDemoApplication Ldap demo 启动类 + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 0:37 + */ +@SpringBootApplication +public class LdapDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(LdapDemoApplication.class, args); + } +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java b/demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java new file mode 100644 index 0000000..ccf9a55 --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java @@ -0,0 +1,80 @@ +package com.xkcoding.ldap.api; + +import lombok.Data; +import org.springframework.lang.Nullable; + +import java.io.Serializable; + +/** + * Result + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:44 + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1696194043024336235L; + + /** + * 错误码 + */ + private int errcode; + + /** + * 错误信息 + */ + private String errmsg; + + /** + * 响应数据 + */ + private T data; + + public Result() { + } + + private Result(ResultCode resultCode) { + this(resultCode.code, resultCode.msg); + } + + private Result(ResultCode resultCode, T data) { + this(resultCode.code, resultCode.msg, data); + } + + private Result(int errcode, String errmsg) { + this(errcode, errmsg, null); + } + + private Result(int errcode, String errmsg, T data) { + this.errcode = errcode; + this.errmsg = errmsg; + this.data = data; + } + + + /** + * 返回成功 + * + * @param 泛型标记 + * @return 响应信息 {@code Result} + */ + public static Result success() { + return new Result<>(ResultCode.SUCCESS); + } + + + /** + * 返回成功-携带数据 + * + * @param data 响应数据 + * @param 泛型标记 + * @return 响应信息 {@code Result} + */ + public static Result success(@Nullable T data) { + return new Result<>(ResultCode.SUCCESS, data); + } + + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java b/demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java new file mode 100644 index 0000000..4a40bb7 --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java @@ -0,0 +1,31 @@ +package com.xkcoding.ldap.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * ResultCode + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:47 + */ +@Getter +@AllArgsConstructor +public enum ResultCode { + + /** + * 接口调用成功 + */ + SUCCESS(0, "Request Successful"), + + /** + * 服务器暂不可用,建议稍候重试。建议重试次数不超过3次。 + */ + FAILURE(-1, "System Busy"); + + final int code; + + final String msg; + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java b/demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java new file mode 100644 index 0000000..ad7ebef --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java @@ -0,0 +1,92 @@ +package com.xkcoding.ldap.entity; + +import lombok.Data; +import org.springframework.ldap.odm.annotations.Attribute; +import org.springframework.ldap.odm.annotations.DnAttribute; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Id; + +import javax.naming.Name; +import java.io.Serializable; + +/** + * People + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 0:51 + */ +@Data +@Entry(base = "ou=people", objectClasses = {"posixAccount", "inetOrgPerson", "top"}) +public class Person implements Serializable { + + private static final long serialVersionUID = -7946768337975852352L; + + @Id + private Name id; + + /** + * 用户id + */ + private String uidNumber; + + /** + * 用户名 + */ + @DnAttribute(value = "uid", index = 1) + private String uid; + + /** + * 姓名 + */ + @Attribute(name = "cn") + private String personName; + + /** + * 密码 + */ + private String userPassword; + + /** + * 名字 + */ + private String givenName; + + /** + * 姓氏 + */ + @Attribute(name = "sn") + private String surname; + + /** + * 邮箱 + */ + private String mail; + + /** + * 职位 + */ + private String title; + + /** + * 部门 + */ + private String departmentNumber; + + /** + * 部门id + */ + private String gidNumber; + + /** + * 根目录 + */ + private String homeDirectory; + + /** + * loginShell + */ + private String loginShell; + + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java b/demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java new file mode 100644 index 0000000..e84471a --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java @@ -0,0 +1,47 @@ +package com.xkcoding.ldap.exception; + +import com.xkcoding.ldap.api.ResultCode; +import lombok.Getter; + +/** + * ServiceException + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:53 + */ +public class ServiceException extends RuntimeException { + + @Getter + private int errcode; + + @Getter + private String errmsg; + + public ServiceException(ResultCode resultCode) { + this(resultCode.getCode(), resultCode.getMsg()); + } + + public ServiceException(String message) { + super(message); + } + + public ServiceException(Integer errcode, String errmsg) { + super(errmsg); + this.errcode = errcode; + this.errmsg = errmsg; + } + + public ServiceException(String message, Throwable cause) { + super(message, cause); + } + + public ServiceException(Throwable cause) { + super(cause); + } + + public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java b/demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java new file mode 100644 index 0000000..5939e2d --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java @@ -0,0 +1,26 @@ +package com.xkcoding.ldap.repository; + +import com.xkcoding.ldap.entity.Person; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import javax.naming.Name; + +/** + * PersonRepository + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:02 + */ +@Repository +public interface PersonRepository extends CrudRepository { + + /** + * 根据用户名查找 + * + * @param uid 用户名 + * @return com.xkcoding.ldap.entity.Person + */ + Person findByUid(String uid); +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java b/demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java new file mode 100644 index 0000000..34bcafd --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java @@ -0,0 +1,21 @@ +package com.xkcoding.ldap.request; + +import lombok.Builder; +import lombok.Data; + +/** + * LoginRequest + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:50 + */ +@Data +@Builder +public class LoginRequest { + + private String username; + + private String password; + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java b/demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java new file mode 100644 index 0000000..c5a07be --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java @@ -0,0 +1,45 @@ +package com.xkcoding.ldap.service; + +import com.xkcoding.ldap.api.Result; +import com.xkcoding.ldap.entity.Person; +import com.xkcoding.ldap.request.LoginRequest; + +/** + * PersonService + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:05 + */ +public interface PersonService { + + /** + * 登录 + * + * @param request {@link LoginRequest} + * @return {@link Result} + */ + Result login(LoginRequest request); + + /** + * 查询全部 + * + * @return {@link Result} + */ + Result listAllPerson(); + + /** + * 保存 + * + * @param person {@link Person} + */ + void save(Person person); + + /** + * 删除 + * + * @param person {@link Person} + */ + void delete(Person person); + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java b/demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java new file mode 100644 index 0000000..05ee7aa --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java @@ -0,0 +1,94 @@ +package com.xkcoding.ldap.service.impl; + +import com.xkcoding.ldap.api.Result; +import com.xkcoding.ldap.entity.Person; +import com.xkcoding.ldap.exception.ServiceException; +import com.xkcoding.ldap.repository.PersonRepository; +import com.xkcoding.ldap.request.LoginRequest; +import com.xkcoding.ldap.service.PersonService; +import com.xkcoding.ldap.util.LdapUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.security.NoSuchAlgorithmException; + +/** + * PersonServiceImpl + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:05 + */ +@Slf4j +@Service +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class PersonServiceImpl implements PersonService { + private final PersonRepository personRepository; + + /** + * 登录 + * + * @param request {@link LoginRequest} + * @return {@link Result} + */ + @Override + public Result login(LoginRequest request) { + log.info("IN LDAP auth"); + + Person user = personRepository.findByUid(request.getUsername()); + + try { + if (ObjectUtils.isEmpty(user)) { + throw new ServiceException("用户名或密码错误,请重新尝试"); + } else { + user.setUserPassword(LdapUtils.asciiToString(user.getUserPassword())); + if (!LdapUtils.verify(user.getUserPassword(), request.getPassword())) { + throw new ServiceException("用户名或密码错误,请重新尝试"); + } + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + log.info("user info:{}", user); + return Result.success(user); + } + + /** + * 查询全部 + * + * @return {@link Result} + */ + @Override + public Result listAllPerson() { + Iterable personList = personRepository.findAll(); + personList.forEach(person -> person.setUserPassword(LdapUtils.asciiToString(person.getUserPassword()))); + return Result.success(personList); + } + + /** + * 保存 + * + * @param person {@link Person} + */ + @Override + public void save(Person person) { + Person p = personRepository.save(person); + log.info("用户{}保存成功", p.getUid()); + } + + /** + * 删除 + * + * @param person {@link Person} + */ + @Override + public void delete(Person person) { + personRepository.delete(person); + log.info("删除用户{}成功", person.getUid()); + } + +} diff --git a/demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java b/demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java new file mode 100644 index 0000000..5b9ede3 --- /dev/null +++ b/demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java @@ -0,0 +1,78 @@ +package com.xkcoding.ldap.util; + +import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * LdapUtils + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:03 + */ +public class LdapUtils { + + /** + * 校验密码 + * + * @param ldapPassword ldap 加密密码 + * @param inputPassword 用户输入 + * @return boolean + * @throws NoSuchAlgorithmException 加解密异常 + */ + public static boolean verify(String ldapPassword, String inputPassword) throws NoSuchAlgorithmException { + + // MessageDigest 提供了消息摘要算法,如 MD5 或 SHA,的功能,这里LDAP使用的是SHA-1 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + + // 取出加密字符 + if (ldapPassword.startsWith("{SSHA}")) { + ldapPassword = ldapPassword.substring(6); + } else if (ldapPassword.startsWith("{SHA}")) { + ldapPassword = ldapPassword.substring(5); + } + // 解码BASE64 + byte[] ldapPasswordByte = Base64.decode(ldapPassword); + byte[] shaCode; + byte[] salt; + + // 前20位是SHA-1加密段,20位后是最初加密时的随机明文 + if (ldapPasswordByte.length <= 20) { + shaCode = ldapPasswordByte; + salt = new byte[0]; + } else { + shaCode = new byte[20]; + salt = new byte[ldapPasswordByte.length - 20]; + System.arraycopy(ldapPasswordByte, 0, shaCode, 0, 20); + System.arraycopy(ldapPasswordByte, 20, salt, 0, salt.length); + } + // 把用户输入的密码添加到摘要计算信息 + md.update(inputPassword.getBytes()); + // 把随机明文添加到摘要计算信息 + md.update(salt); + + // 按SSHA把当前用户密码进行计算 + byte[] inputPasswordByte = md.digest(); + + // 返回校验结果 + return MessageDigest.isEqual(shaCode, inputPasswordByte); + } + + /** + * Ascii转换为字符串 + * + * @param value Ascii串 + * @return 字符串 + */ + public static String asciiToString(String value) { + StringBuilder sbu = new StringBuilder(); + String[] chars = value.split(","); + for (String aChar : chars) { + sbu.append((char) Integer.parseInt(aChar)); + } + return sbu.toString(); + } + +} diff --git a/spring-boot-demo-ldap/src/main/resources/application.yml b/demo-ldap/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-ldap/src/main/resources/application.yml rename to demo-ldap/src/main/resources/application.yml diff --git a/demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java b/demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java new file mode 100644 index 0000000..377a097 --- /dev/null +++ b/demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java @@ -0,0 +1,85 @@ +package com.xkcoding.ldap; + +import com.xkcoding.ldap.api.Result; +import com.xkcoding.ldap.entity.Person; +import com.xkcoding.ldap.request.LoginRequest; +import com.xkcoding.ldap.service.PersonService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +/** + * LdapDemoApplicationTest + * + * @author fxbin + * @version v1.0 + * @since 2019-08-26 1:06 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class LdapDemoApplicationTests { + + @Resource + private PersonService personService; + + @Test + public void contextLoads() { + } + + /** + * 测试查询单个 + */ + @Test + public void loginTest() { + LoginRequest loginRequest = LoginRequest.builder().username("wangwu").password("123456").build(); + Result login = personService.login(loginRequest); + System.out.println(login); + } + + /** + * 测试查询列表 + */ + @Test + public void listAllPersonTest() { + Result result = personService.listAllPerson(); + System.out.println(result); + } + + /** + * 测试保存 + */ + @Test + public void saveTest() { + Person person = new Person(); + + person.setUid("zhaosi"); + + person.setSurname("赵"); + person.setGivenName("四"); + person.setUserPassword("123456"); + + // required field + person.setPersonName("赵四"); + person.setUidNumber("666"); + person.setGidNumber("666"); + person.setHomeDirectory("/home/zhaosi"); + person.setLoginShell("/bin/bash"); + + personService.save(person); + } + + /** + * 测试删除 + */ + @Test + public void deleteTest() { + Person person = new Person(); + person.setUid("zhaosi"); + + personService.delete(person); + } + +} diff --git a/spring-boot-demo-log-aop/.gitignore b/demo-log-aop/.gitignore similarity index 100% rename from spring-boot-demo-log-aop/.gitignore rename to demo-log-aop/.gitignore diff --git a/demo-log-aop/README.md b/demo-log-aop/README.md new file mode 100644 index 0000000..14fea80 --- /dev/null +++ b/demo-log-aop/README.md @@ -0,0 +1,281 @@ +# spring-boot-demo-log-aop + +> 此 demo 主要是演示如何使用 aop 切面对请求进行日志记录,并且记录 UserAgent 信息。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-log-aop + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-log-aop + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + eu.bitwalker + UserAgentUtils + + + + + spring-boot-demo-log-aop + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## AopLog.java + +```java +/** + *

+ * 使用 aop 切面记录请求日志信息 + *

+ * + * @author yangkai.shen + * @author chen qi + * @date Created in 2018-10-01 22:05 + */ +@Aspect +@Component +@Slf4j +public class AopLog { + /** + * 切入点 + */ + @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))") + public void log() { + + } + + /** + * 环绕操作 + * + * @param point 切入点 + * @return 原方法返回值 + * @throws Throwable 异常信息 + */ + @Around("log()") + public Object aroundLog(ProceedingJoinPoint point) throws Throwable { + + // 开始打印请求日志 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); + + // 打印请求相关参数 + long startTime = System.currentTimeMillis(); + Object result = point.proceed(); + String header = request.getHeader("User-Agent"); + UserAgent userAgent = UserAgent.parseUserAgentString(header); + + final Log l = Log.builder() + .threadId(Long.toString(Thread.currentThread().getId())) + .threadName(Thread.currentThread().getName()) + .ip(getIp(request)) + .url(request.getRequestURL().toString()) + .classMethod(String.format("%s.%s", point.getSignature().getDeclaringTypeName(), + point.getSignature().getName())) + .httpMethod(request.getMethod()) + .requestParams(getNameAndValue(point)) + .result(result) + .timeCost(System.currentTimeMillis() - startTime) + .userAgent(header) + .browser(userAgent.getBrowser().toString()) + .os(userAgent.getOperatingSystem().toString()).build(); + + log.info("Request Log Info : {}", JSONUtil.toJsonStr(l)); + + return result; + } + + /** + * 获取方法参数名和参数值 + * @param joinPoint + * @return + */ + private Map getNameAndValue(ProceedingJoinPoint joinPoint) { + + final Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + final String[] names = methodSignature.getParameterNames(); + final Object[] args = joinPoint.getArgs(); + + if (ArrayUtil.isEmpty(names) || ArrayUtil.isEmpty(args)) { + return Collections.emptyMap(); + } + if (names.length != args.length) { + log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName()); + return Collections.emptyMap(); + } + Map map = Maps.newHashMap(); + for (int i = 0; i < names.length; i++) { + map.put(names[i], args[i]); + } + return map; + } + + private static final String UNKNOWN = "unknown"; + + /** + * 获取ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage(), e); + } + } + return ip; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + static class Log { + // 线程id + private String threadId; + // 线程名称 + private String threadName; + // ip + private String ip; + // url + private String url; + // http方法 GET POST PUT DELETE PATCH + private String httpMethod; + // 类方法 + private String classMethod; + // 请求参数 + private Object requestParams; + // 返回参数 + private Object result; + // 接口耗时 + private Long timeCost; + // 操作系统 + private String os; + // 浏览器 + private String browser; + // user-agent + private String userAgent; + } +} +``` + +## TestController.java + +```java +/** + *

+ * 测试 Controller + *

+ * + * @author yangkai.shen + * @author chen qi + * @date Created in 2018-10-01 22:10 + */ +@Slf4j +@RestController +public class TestController { + + /** + * 测试方法 + * + * @param who 测试参数 + * @return {@link Dict} + */ + @GetMapping("/test") + public Dict test(String who) { + return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who); + } + + /** + * 测试post json方法 + * @param map 请求的json参数 + * @return {@link Dict} + */ + @PostMapping("/testJson") + public Dict testJson(@RequestBody Map map) { + + final String jsonStr = JSONUtil.toJsonStr(map); + log.info(jsonStr); + return Dict.create().set("json", map); + } +} +``` + diff --git a/demo-log-aop/pom.xml b/demo-log-aop/pom.xml new file mode 100644 index 0000000..a01664f --- /dev/null +++ b/demo-log-aop/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + demo-log-aop + 1.0.0-SNAPSHOT + jar + + demo-log-aop + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + eu.bitwalker + UserAgentUtils + + + + + demo-log-aop + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java b/demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java new file mode 100644 index 0000000..32d225c --- /dev/null +++ b/demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.log.aop; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-10-01 22:05 + */ +@SpringBootApplication +public class SpringBootDemoLogAopApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoLogAopApplication.class, args); + } +} diff --git a/demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java b/demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java new file mode 100644 index 0000000..d39c093 --- /dev/null +++ b/demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java @@ -0,0 +1,178 @@ +package com.xkcoding.log.aop.aspectj; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Maps; +import eu.bitwalker.useragentutils.UserAgent; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + *

+ * 使用 aop 切面记录请求日志信息 + *

+ * + * @author yangkai.shen + * @author chen qi + * @date Created in 2018-10-01 22:05 + */ +@Aspect +@Component +@Slf4j +public class AopLog { + /** + * 切入点 + */ + @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))") + public void log() { + + } + + /** + * 环绕操作 + * + * @param point 切入点 + * @return 原方法返回值 + * @throws Throwable 异常信息 + */ + @Around("log()") + public Object aroundLog(ProceedingJoinPoint point) throws Throwable { + + // 开始打印请求日志 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); + + // 打印请求相关参数 + long startTime = System.currentTimeMillis(); + Object result = point.proceed(); + String header = request.getHeader("User-Agent"); + UserAgent userAgent = UserAgent.parseUserAgentString(header); + + final Log l = Log.builder() + .threadId(Long.toString(Thread.currentThread().getId())) + .threadName(Thread.currentThread().getName()) + .ip(getIp(request)) + .url(request.getRequestURL().toString()) + .classMethod(String.format("%s.%s", point.getSignature().getDeclaringTypeName(), + point.getSignature().getName())) + .httpMethod(request.getMethod()) + .requestParams(getNameAndValue(point)) + .result(result) + .timeCost(System.currentTimeMillis() - startTime) + .userAgent(header) + .browser(userAgent.getBrowser().toString()) + .os(userAgent.getOperatingSystem().toString()).build(); + + log.info("Request Log Info : {}", JSONUtil.toJsonStr(l)); + + return result; + } + + /** + * 获取方法参数名和参数值 + * @param joinPoint + * @return + */ + private Map getNameAndValue(ProceedingJoinPoint joinPoint) { + + final Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + final String[] names = methodSignature.getParameterNames(); + final Object[] args = joinPoint.getArgs(); + + if (ArrayUtil.isEmpty(names) || ArrayUtil.isEmpty(args)) { + return Collections.emptyMap(); + } + if (names.length != args.length) { + log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName()); + return Collections.emptyMap(); + } + Map map = Maps.newHashMap(); + for (int i = 0; i < names.length; i++) { + map.put(names[i], args[i]); + } + return map; + } + + private static final String UNKNOWN = "unknown"; + + /** + * 获取ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage(), e); + } + } + return ip; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + static class Log { + // 线程id + private String threadId; + // 线程名称 + private String threadName; + // ip + private String ip; + // url + private String url; + // http方法 GET POST PUT DELETE PATCH + private String httpMethod; + // 类方法 + private String classMethod; + // 请求参数 + private Object requestParams; + // 返回参数 + private Object result; + // 接口耗时 + private Long timeCost; + // 操作系统 + private String os; + // 浏览器 + private String browser; + // user-agent + private String userAgent; + } +} diff --git a/demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java b/demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java new file mode 100644 index 0000000..c261d79 --- /dev/null +++ b/demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java @@ -0,0 +1,50 @@ +package com.xkcoding.log.aop.controller; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + *

+ * 测试 Controller + *

+ * + * @author yangkai.shen + * @author chen qi + * @date Created in 2018-10-01 22:10 + */ +@Slf4j +@RestController +public class TestController { + + /** + * 测试方法 + * + * @param who 测试参数 + * @return {@link Dict} + */ + @GetMapping("/test") + public Dict test(String who) { + return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who); + } + + /** + * 测试post json方法 + * @param map 请求的json参数 + * @return {@link Dict} + */ + @PostMapping("/testJson") + public Dict testJson(@RequestBody Map map) { + + final String jsonStr = JSONUtil.toJsonStr(map); + log.info(jsonStr); + return Dict.create().set("json", map); + } +} diff --git a/spring-boot-demo-log-aop/src/main/resources/application.yml b/demo-log-aop/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-log-aop/src/main/resources/application.yml rename to demo-log-aop/src/main/resources/application.yml diff --git a/demo-log-aop/src/main/resources/logback-spring.xml b/demo-log-aop/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..86bf301 --- /dev/null +++ b/demo-log-aop/src/main/resources/logback-spring.xml @@ -0,0 +1,77 @@ + + + + + + INFO + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + + ERROR + + DENY + + ACCEPT + + + + + + + logs/demo-log-aop/info.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + + + 2MB + + + + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + Error + + + + + + + logs/demo-log-aop/error.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + 2MB + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + + + diff --git a/demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java b/demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java new file mode 100644 index 0000000..af44ad9 --- /dev/null +++ b/demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.log.aop; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoLogAopApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-logback/.gitignore b/demo-logback/.gitignore similarity index 100% rename from spring-boot-demo-logback/.gitignore rename to demo-logback/.gitignore diff --git a/demo-logback/README.md b/demo-logback/README.md new file mode 100644 index 0000000..d7d6988 --- /dev/null +++ b/demo-logback/README.md @@ -0,0 +1,178 @@ +# spring-boot-demo-logback + +> 此 demo 主要演示了如何使用 logback 记录程序运行过程中的日志,以及如何配置 logback,可以同时生成控制台日志和文件日志记录,文件日志以日期和大小进行拆分生成。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-logback + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-logback + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-logback + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoLogbackApplication.java + +```java +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-09-30 23:16 + */ +@SpringBootApplication +@Slf4j +public class SpringBootDemoLogbackApplication { + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoLogbackApplication.class, args); + int length = context.getBeanDefinitionNames().length; + log.trace("Spring boot启动初始化了 {} 个 Bean", length); + log.debug("Spring boot启动初始化了 {} 个 Bean", length); + log.info("Spring boot启动初始化了 {} 个 Bean", length); + log.warn("Spring boot启动初始化了 {} 个 Bean", length); + log.error("Spring boot启动初始化了 {} 个 Bean", length); + try { + int i = 0; + int j = 1 / i; + } catch (Exception e) { + log.error("【SpringBootDemoLogbackApplication】启动异常:", e); + } + } +} +``` + +## logback-spring.xml + +```xml + + + + + + INFO + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + + ERROR + + DENY + + ACCEPT + + + + + + + logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + + + 2MB + + + + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + Error + + + + + + + logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + 2MB + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + UTF-8 + + + + + + + + + +``` + diff --git a/demo-logback/pom.xml b/demo-logback/pom.xml new file mode 100644 index 0000000..174a1cd --- /dev/null +++ b/demo-logback/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + demo-logback + 1.0.0-SNAPSHOT + jar + + demo-logback + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-logback + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java b/demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java new file mode 100644 index 0000000..217ee02 --- /dev/null +++ b/demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java @@ -0,0 +1,35 @@ +package com.xkcoding.logback; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + *

+ * 启动类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-09-30 23:16 + */ +@SpringBootApplication +@Slf4j +public class SpringBootDemoLogbackApplication { + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoLogbackApplication.class, args); + int length = context.getBeanDefinitionNames().length; + log.trace("Spring boot启动初始化了 {} 个 Bean", length); + log.debug("Spring boot启动初始化了 {} 个 Bean", length); + log.info("Spring boot启动初始化了 {} 个 Bean", length); + log.warn("Spring boot启动初始化了 {} 个 Bean", length); + log.error("Spring boot启动初始化了 {} 个 Bean", length); + try { + int i = 0; + int j = 1 / i; + } catch (Exception e) { + log.error("【SpringBootDemoLogbackApplication】启动异常:", e); + } + } +} diff --git a/spring-boot-demo-logback/src/main/resources/application.yml b/demo-logback/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-logback/src/main/resources/application.yml rename to demo-logback/src/main/resources/application.yml diff --git a/demo-logback/src/main/resources/logback-spring.xml b/demo-logback/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..dcd48fe --- /dev/null +++ b/demo-logback/src/main/resources/logback-spring.xml @@ -0,0 +1,79 @@ + + + + + + + INFO + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + + + ERROR + + DENY + + ACCEPT + + + + + + + logs/demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + + + 2MB + + + + + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + + + + Error + + + + + + + logs/demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log + + 90 + + + 2MB + + + + ${FILE_ERROR_PATTERN} + UTF-8 + + + + + + + + + diff --git a/demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java b/demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java new file mode 100644 index 0000000..53bbb7f --- /dev/null +++ b/demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.logback; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoLogbackApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-mongodb/.gitignore b/demo-mongodb/.gitignore similarity index 100% rename from spring-boot-demo-mongodb/.gitignore rename to demo-mongodb/.gitignore diff --git a/demo-mongodb/README.md b/demo-mongodb/README.md new file mode 100644 index 0000000..2c6b85d --- /dev/null +++ b/demo-mongodb/README.md @@ -0,0 +1,317 @@ +# spring-boot-demo-mongodb + +> 此 demo 主要演示了 Spring Boot 如何集成 MongoDB,使用官方的 starter 实现增删改查。 + +## 注意 + +作者编写本demo时,MongoDB 最新版本为 `4.1`,使用 docker 运行,下面是所有步骤: + +1. 下载镜像:`docker pull mongo:4.1` +2. 运行容器:`docker run -d -p 27017:27017 -v /Users/yangkai.shen/docker/mongo/data:/data/db --name mongo-4.1 mongo:4.1` +3. 停止容器:`docker stop mongo-4.1` +4. 启动容器:`docker start mongo-4.1` + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-mongodb + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-mongodb + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-mongodb + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + data: + mongodb: + host: localhost + port: 27017 + database: article_db +logging: + level: + org.springframework.data.mongodb.core: debug +``` + +## Article.java + +```java +/** + *

+ * 文章实体类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:21 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Article { + /** + * 文章id + */ + @Id + private Long id; + + /** + * 文章标题 + */ + private String title; + + /** + * 文章内容 + */ + private String content; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 点赞数量 + */ + private Long thumbUp; + + /** + * 访客数量 + */ + private Long visits; + +} +``` + +## ArticleRepository.java + +```java +/** + *

+ * 文章 Dao + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:30 + */ +public interface ArticleRepository extends MongoRepository { + /** + * 根据标题模糊查询 + * + * @param title 标题 + * @return 满足条件的文章列表 + */ + List
findByTitleLike(String title); +} +``` + +## ArticleRepositoryTest.java + +```java +/** + *

+ * 测试操作 MongoDb + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:35 + */ +@Slf4j +public class ArticleRepositoryTest extends SpringBootDemoMongodbApplicationTests { + @Autowired + private ArticleRepository articleRepo; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private Snowflake snowflake; + + /** + * 测试新增 + */ + @Test + public void testSave() { + Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil + .date(), 0L, 0L); + articleRepo.save(article); + log.info("【article】= {}", JSONUtil.toJsonStr(article)); + } + + /** + * 测试新增列表 + */ + @Test + public void testSaveList() { + List
articles = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil + .date(), DateUtil.date(), 0L, 0L)); + } + articleRepo.saveAll(articles); + + log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream() + .map(Article::getId) + .collect(Collectors.toList()))); + } + + /** + * 测试更新 + */ + @Test + public void testUpdate() { + articleRepo.findById(1L).ifPresent(article -> { + article.setTitle(article.getTitle() + "更新之后的标题"); + article.setUpdateTime(DateUtil.date()); + articleRepo.save(article); + log.info("【article】= {}", JSONUtil.toJsonStr(article)); + }); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + // 根据主键删除 + articleRepo.deleteById(1L); + + // 全部删除 + articleRepo.deleteAll(); + } + + /** + * 测试点赞数、访客数,使用save方式更新点赞、访客 + */ + @Test + public void testThumbUp() { + articleRepo.findById(1L).ifPresent(article -> { + article.setThumbUp(article.getThumbUp() + 1); + article.setVisits(article.getVisits() + 1); + articleRepo.save(article); + log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits()); + }); + } + + /** + * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客 + */ + @Test + public void testThumbUp2() { + Query query = new Query(); + query.addCriteria(Criteria.where("_id").is(1L)); + Update update = new Update(); + update.inc("thumbUp", 1L); + update.inc("visits", 1L); + mongoTemplate.updateFirst(query, update, "article"); + + articleRepo.findById(1L) + .ifPresent(article -> log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article + .getVisits())); + } + + /** + * 测试分页排序查询 + */ + @Test + public void testQuery() { + Sort sort = Sort.by("thumbUp", "updateTime").descending(); + PageRequest pageRequest = PageRequest.of(0, 5, sort); + Page
all = articleRepo.findAll(pageRequest); + log.info("【总页数】= {}", all.getTotalPages()); + log.info("【总条数】= {}", all.getTotalElements()); + log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent() + .stream() + .map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp() + "更新时间:" + article.getUpdateTime()) + .collect(Collectors.toList()))); + } + + /** + * 测试根据标题模糊查询 + */ + @Test + public void testFindByTitleLike() { + List
articles = articleRepo.findByTitleLike("更新"); + log.info("【articles】= {}", JSONUtil.toJsonStr(articles)); + } + +} +``` + +## 参考 + +1. Spring Data MongoDB 官方文档:https://docs.spring.io/spring-data/mongodb/docs/2.1.2.RELEASE/reference/html/ +2. MongoDB 官方镜像地址:https://hub.docker.com/_/mongo +3. MongoDB 官方快速入门:https://docs.mongodb.com/manual/tutorial/getting-started/ +4. MongoDB 官方文档:https://docs.mongodb.com/manual/ diff --git a/demo-mongodb/pom.xml b/demo-mongodb/pom.xml new file mode 100644 index 0000000..b63c3d4 --- /dev/null +++ b/demo-mongodb/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-mongodb + 1.0.0-SNAPSHOT + jar + + demo-mongodb + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-mongodb + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java b/demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java new file mode 100644 index 0000000..8c6fd8b --- /dev/null +++ b/demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java @@ -0,0 +1,30 @@ +package com.xkcoding.mongodb; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:14 + */ +@SpringBootApplication +public class SpringBootDemoMongodbApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMongodbApplication.class, args); + } + + @Bean + public Snowflake snowflake() { + return IdUtil.createSnowflake(1, 1); + } + +} + diff --git a/demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java b/demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java new file mode 100644 index 0000000..1d7fcd5 --- /dev/null +++ b/demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java @@ -0,0 +1,60 @@ +package com.xkcoding.mongodb.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +import java.util.Date; + +/** + *

+ * 文章实体类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:21 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Article { + /** + * 文章id + */ + @Id + private Long id; + + /** + * 文章标题 + */ + private String title; + + /** + * 文章内容 + */ + private String content; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 点赞数量 + */ + private Long thumbUp; + + /** + * 访客数量 + */ + private Long visits; + +} diff --git a/demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java b/demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java new file mode 100644 index 0000000..341fd62 --- /dev/null +++ b/demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java @@ -0,0 +1,24 @@ +package com.xkcoding.mongodb.repository; + +import com.xkcoding.mongodb.model.Article; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +/** + *

+ * 文章 Dao + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:30 + */ +public interface ArticleRepository extends MongoRepository { + /** + * 根据标题模糊查询 + * + * @param title 标题 + * @return 满足条件的文章列表 + */ + List
findByTitleLike(String title); +} diff --git a/spring-boot-demo-mongodb/src/main/resources/application.yml b/demo-mongodb/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-mongodb/src/main/resources/application.yml rename to demo-mongodb/src/main/resources/application.yml diff --git a/spring-boot-demo-mongodb/src/test/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplicationTests.java b/demo-mongodb/src/test/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplicationTests.java similarity index 100% rename from spring-boot-demo-mongodb/src/test/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplicationTests.java rename to demo-mongodb/src/test/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplicationTests.java diff --git a/demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java b/demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java new file mode 100644 index 0000000..6617c73 --- /dev/null +++ b/demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java @@ -0,0 +1,142 @@ +package com.xkcoding.mongodb.repository; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Lists; +import com.xkcoding.mongodb.SpringBootDemoMongodbApplicationTests; +import com.xkcoding.mongodb.model.Article; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 测试操作 MongoDb + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:35 + */ +@Slf4j +public class ArticleRepositoryTest extends SpringBootDemoMongodbApplicationTests { + @Autowired + private ArticleRepository articleRepo; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private Snowflake snowflake; + + /** + * 测试新增 + */ + @Test + public void testSave() { + Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil.date(), 0L, 0L); + articleRepo.save(article); + log.info("【article】= {}", JSONUtil.toJsonStr(article)); + } + + /** + * 测试新增列表 + */ + @Test + public void testSaveList() { + List
articles = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil.date(), 0L, 0L)); + } + articleRepo.saveAll(articles); + + log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream().map(Article::getId).collect(Collectors.toList()))); + } + + /** + * 测试更新 + */ + @Test + public void testUpdate() { + articleRepo.findById(1L).ifPresent(article -> { + article.setTitle(article.getTitle() + "更新之后的标题"); + article.setUpdateTime(DateUtil.date()); + articleRepo.save(article); + log.info("【article】= {}", JSONUtil.toJsonStr(article)); + }); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + // 根据主键删除 + articleRepo.deleteById(1L); + + // 全部删除 + articleRepo.deleteAll(); + } + + /** + * 测试点赞数、访客数,使用save方式更新点赞、访客 + */ + @Test + public void testThumbUp() { + articleRepo.findById(1L).ifPresent(article -> { + article.setThumbUp(article.getThumbUp() + 1); + article.setVisits(article.getVisits() + 1); + articleRepo.save(article); + log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits()); + }); + } + + /** + * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客 + */ + @Test + public void testThumbUp2() { + Query query = new Query(); + query.addCriteria(Criteria.where("_id").is(1L)); + Update update = new Update(); + update.inc("thumbUp", 1L); + update.inc("visits", 1L); + mongoTemplate.updateFirst(query, update, "article"); + + articleRepo.findById(1L).ifPresent(article -> log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits())); + } + + /** + * 测试分页排序查询 + */ + @Test + public void testQuery() { + Sort sort = Sort.by("thumbUp", "updateTime").descending(); + PageRequest pageRequest = PageRequest.of(0, 5, sort); + Page
all = articleRepo.findAll(pageRequest); + log.info("【总页数】= {}", all.getTotalPages()); + log.info("【总条数】= {}", all.getTotalElements()); + log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent().stream().map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp() + "更新时间:" + article.getUpdateTime()).collect(Collectors.toList()))); + } + + /** + * 测试根据标题模糊查询 + */ + @Test + public void testFindByTitleLike() { + List
articles = articleRepo.findByTitleLike("更新"); + log.info("【articles】= {}", JSONUtil.toJsonStr(articles)); + } + +} diff --git a/spring-boot-demo-mq-kafka/.gitignore b/demo-mq-kafka/.gitignore similarity index 100% rename from spring-boot-demo-mq-kafka/.gitignore rename to demo-mq-kafka/.gitignore diff --git a/demo-mq-kafka/README.md b/demo-mq-kafka/README.md new file mode 100644 index 0000000..55b684e --- /dev/null +++ b/demo-mq-kafka/README.md @@ -0,0 +1,255 @@ +# spring-boot-demo-mq-kafka + +> 本 demo 主要演示了 Spring Boot 如何集成 kafka,实现消息的发送和接收。 + +## 环境准备 + +> 注意:本 demo 基于 Spring Boot 2.1.0.RELEASE 版本,因此 spring-kafka 的版本为 2.2.0.RELEASE,kafka-clients 的版本为2.0.0,所以 kafka 的版本选用为 kafka_2.11-2.1.0 + +创建一个名为 `test` 的Topic + +```bash +./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test +``` + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-mq-kafka + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-mq-kafka + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.kafka + spring-kafka + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + spring-boot-demo-mq-kafka + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + kafka: + bootstrap-servers: localhost:9092 + producer: + retries: 0 + batch-size: 16384 + buffer-memory: 33554432 + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + consumer: + group-id: spring-boot-demo + # 手动提交 + enable-auto-commit: false + auto-offset-reset: latest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + properties: + session.timeout.ms: 60000 + listener: + log-container-config: false + concurrency: 5 + # 手动提交 + ack-mode: manual_immediate +``` + +## KafkaConfig.java + +```java +/** + *

+ * kafka配置类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:49 + */ +@Configuration +@EnableConfigurationProperties({KafkaProperties.class}) +@EnableKafka +@AllArgsConstructor +public class KafkaConfig { + private final KafkaProperties kafkaProperties; + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + + @Bean + public ProducerFactory producerFactory() { + return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties()); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); + factory.setBatchListener(true); + factory.getContainerProperties().setPollTimeout(3000); + return factory; + } + + @Bean + public ConsumerFactory consumerFactory() { + return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); + } + + @Bean("ackContainerFactory") + public ConcurrentKafkaListenerContainerFactory ackContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); + return factory; + } + +} +``` + +## MessageHandler.java + +```java +/** + *

+ * 消息处理器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:58 + */ +@Component +@Slf4j +public class MessageHandler { + + @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "ackContainerFactory") + public void handleMessage(ConsumerRecord record, Acknowledgment acknowledgment) { + try { + String message = (String) record.value(); + log.info("收到消息: {}", message); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + // 手动提交 offset + acknowledgment.acknowledge(); + } + } +} +``` + +## SpringBootDemoMqKafkaApplicationTests.java + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoMqKafkaApplicationTests { + @Autowired + private KafkaTemplate kafkaTemplate; + + /** + * 测试发送消息 + */ + @Test + public void testSend() { + kafkaTemplate.send(KafkaConsts.TOPIC_TEST, "hello,kafka..."); + } + +} +``` + +## 参考 + +1. Spring Boot 版本和 Spring-Kafka 的版本对应关系:https://spring.io/projects/spring-kafka + + | Spring for Apache Kafka Version | Spring Integration for Apache Kafka Version | kafka-clients | + | ------------------------------- | ------------------------------------------- | ------------------- | + | 2.2.x | 3.1.x | 2.0.0, 2.1.0 | + | 2.1.x | 3.0.x | 1.0.x, 1.1.x, 2.0.0 | + | 2.0.x | 3.0.x | 0.11.0.x, 1.0.x | + | 1.3.x | 2.3.x | 0.11.0.x, 1.0.x | + | 1.2.x | 2.2.x | 0.10.2.x | + | 1.1.x | 2.1.x | 0.10.0.x, 0.10.1.x | + | 1.0.x | 2.0.x | 0.9.x.x | + | N/A* | 1.3.x | 0.8.2.2 | + + > **IMPORTANT:** This matrix is client compatibility; in most cases (since 0.10.2.0) newer clients can communicate with older brokers. All users with brokers >= 0.10.x.x **(and all spring boot 1.5.x users)** are recommended to use spring-kafka version 1.3.x or higher due to its simpler threading model thanks to [KIP-62](https://cwiki.apache.org/confluence/display/KAFKA/KIP-62%3A+Allow+consumer+to+send+heartbeats+from+a+background+thread). For a complete discussion about client/broker compatibility, see the Kafka [Compatibility Matrix](https://cwiki.apache.org/confluence/display/KAFKA/Compatibility+Matrix) + > + > - Spring Integration Kafka versions prior to 2.0 pre-dated the Spring for Apache Kafka project and therefore were not based on it. + > + > These versions will be referenced transitively when using maven or gradle for version management. For the 1.1.x version, the 0.10.1.x is the default. + > + > 2.1.x uses the 1.1.x kafka-clients by default. When overriding the kafka-clients for 2.1.x see [the documentation appendix](https://docs.spring.io/spring-kafka/docs/2.1.x/reference/html/deps-for-11x.html). + > + > 2.2.x uses the 2.0.x kafka-clients by default. When overriding the kafka-clients for 2.2.x see [the documentation appendix](https://docs.spring.io/spring-kafka/docs/2.2.1.BUILD-SNAPSHOT/reference/html/deps-for-21x.html). + > + > - Spring Boot 1.5 users should use 1.3.x (Boot dependency management will use 1.1.x by default so this should be overridden). + > - Spring Boot 2.0 users should use 2.0.x (Boot dependency management will use the correct version). + > - Spring Boot 2.1 users should use 2.2.x (Boot dependency management will use the correct version). + +2. Spring-Kafka 官方文档:https://docs.spring.io/spring-kafka/docs/2.2.0.RELEASE/reference/html/ diff --git a/demo-mq-kafka/pom.xml b/demo-mq-kafka/pom.xml new file mode 100644 index 0000000..6168aa9 --- /dev/null +++ b/demo-mq-kafka/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-mq-kafka + 1.0.0-SNAPSHOT + jar + + demo-mq-kafka + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.kafka + spring-kafka + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + demo-mq-kafka + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java new file mode 100644 index 0000000..c2e8e5e --- /dev/null +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.mq.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

+ * 启动器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:43 + */ +@SpringBootApplication +public class SpringBootDemoMqKafkaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMqKafkaApplication.class, args); + } + +} + diff --git a/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java new file mode 100644 index 0000000..b7d9c75 --- /dev/null +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java @@ -0,0 +1,63 @@ +package com.xkcoding.mq.kafka.config; + +import com.xkcoding.mq.kafka.constants.KafkaConsts; +import lombok.AllArgsConstructor; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.*; +import org.springframework.kafka.listener.ContainerProperties; + +/** + *

+ * kafka配置类 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:49 + */ +@Configuration +@EnableConfigurationProperties({KafkaProperties.class}) +@EnableKafka +@AllArgsConstructor +public class KafkaConfig { + private final KafkaProperties kafkaProperties; + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + + @Bean + public ProducerFactory producerFactory() { + return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties()); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); + factory.setBatchListener(true); + factory.getContainerProperties().setPollTimeout(3000); + return factory; + } + + @Bean + public ConsumerFactory consumerFactory() { + return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); + } + + @Bean("ackContainerFactory") + public ConcurrentKafkaListenerContainerFactory ackContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); + return factory; + } + +} diff --git a/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java new file mode 100644 index 0000000..3546abb --- /dev/null +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java @@ -0,0 +1,21 @@ +package com.xkcoding.mq.kafka.constants; + +/** + *

+ * kafka 常量池 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:52 + */ +public interface KafkaConsts { + /** + * 默认分区大小 + */ + Integer DEFAULT_PARTITION_NUM = 3; + + /** + * Topic 名称 + */ + String TOPIC_TEST = "test"; +} diff --git a/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java new file mode 100644 index 0000000..61dee17 --- /dev/null +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java @@ -0,0 +1,34 @@ +package com.xkcoding.mq.kafka.handler; + +import com.xkcoding.mq.kafka.constants.KafkaConsts; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Component; + +/** + *

+ * 消息处理器 + *

+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:58 + */ +@Component +@Slf4j +public class MessageHandler { + + @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "ackContainerFactory") + public void handleMessage(ConsumerRecord record, Acknowledgment acknowledgment) { + try { + String message = (String) record.value(); + log.info("收到消息: {}", message); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + // 手动提交 offset + acknowledgment.acknowledge(); + } + } +} diff --git a/spring-boot-demo-mq-kafka/src/main/resources/application.yml b/demo-mq-kafka/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-mq-kafka/src/main/resources/application.yml rename to demo-mq-kafka/src/main/resources/application.yml diff --git a/spring-boot-demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java b/demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java similarity index 100% rename from spring-boot-demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java rename to demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java diff --git a/spring-boot-demo-mq-rabbitmq/.gitignore b/demo-mq-rabbitmq/.gitignore similarity index 100% rename from spring-boot-demo-mq-rabbitmq/.gitignore rename to demo-mq-rabbitmq/.gitignore diff --git a/demo-mq-rabbitmq/README.md b/demo-mq-rabbitmq/README.md new file mode 100644 index 0000000..d7fe45e --- /dev/null +++ b/demo-mq-rabbitmq/README.md @@ -0,0 +1,534 @@ +# spring-boot-demo-mq-rabbitmq + +> 此 demo 主要演示了 Spring Boot 如何集成 RabbitMQ,并且演示了基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收。 + +## 注意 + +作者编写本demo时,RabbitMQ 版本使用 `3.7.7-management`,使用 docker 运行,下面是所有步骤: + +1. 下载镜像:`docker pull rabbitmq:3.7.7-management` + +2. 运行容器:`docker run -d -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 --name rabbit-3.7.7 rabbitmq:3.7.7-management` + +3. 进入容器:`docker exec -it rabbit-3.7.7 /bin/bash` + +4. 给容器安装 下载工具 wget:`apt-get install -y wget` + +5. 下载插件包,因为我们的 `RabbitMQ` 版本为 `3.7.7` 所以我们安装 `3.7.x` 版本的延迟队列插件 + + ```bash + root@f72ac937f2be:/plugins# wget https://dl.bintray.com/rabbitmq/community-plugins/3.7.x/rabbitmq_delayed_message_exchange/rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + ``` + +6. 给容器安装 解压工具 unzip:`apt-get install -y unzip` + +7. 解压插件包 + + ```bash + root@f72ac937f2be:/plugins# unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + Archive: rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + inflating: rabbitmq_delayed_message_exchange-20171201-3.7.x.ez + ``` + +8. 启动延迟队列插件 + + ```yaml + root@f72ac937f2be:/plugins# rabbitmq-plugins enable rabbitmq_delayed_message_exchange + The following plugins have been configured: + rabbitmq_delayed_message_exchange + rabbitmq_management + rabbitmq_management_agent + rabbitmq_web_dispatch + Applying plugin configuration to rabbit@f72ac937f2be... + The following plugins have been enabled: + rabbitmq_delayed_message_exchange + + started 1 plugins. + ``` + +9. 退出容器:`exit` + +10. 停止容器:`docker stop rabbit-3.7.7` + +11. 启动容器:`docker start rabbit-3.7.7` + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-mq-rabbitmq + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-mq-rabbitmq + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + spring-boot-demo-mq-rabbitmq + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / + # 手动提交消息 + listener: + simple: + acknowledge-mode: manual + direct: + acknowledge-mode: manual +``` + +## RabbitConsts.java + +```java +/** + *

+ * RabbitMQ常量池 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 17:08 + */ +public interface RabbitConsts { + /** + * 直接模式1 + */ + String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; + + /** + * 队列2 + */ + String QUEUE_TWO = "queue.2"; + + /** + * 队列3 + */ + String QUEUE_THREE = "3.queue"; + + /** + * 分列模式 + */ + String FANOUT_MODE_QUEUE = "fanout.mode"; + + /** + * 主题模式 + */ + String TOPIC_MODE_QUEUE = "topic.mode"; + + /** + * 路由1 + */ + String TOPIC_ROUTING_KEY_ONE = "queue.#"; + + /** + * 路由2 + */ + String TOPIC_ROUTING_KEY_TWO = "*.queue"; + + /** + * 路由3 + */ + String TOPIC_ROUTING_KEY_THREE = "3.queue"; + + /** + * 延迟队列 + */ + String DELAY_QUEUE = "delay.queue"; + + /** + * 延迟队列交换器 + */ + String DELAY_MODE_QUEUE = "delay.mode"; +} +``` + +## RabbitMqConfig.java + +> RoutingKey规则 +> +> - 路由格式必须以 `.` 分隔,比如 `user.email` 或者 `user.aaa.email` +> - 通配符 `*` ,代表一个占位符,或者说一个单词,比如路由为 `user.*`,那么 **`user.email`** 可以匹配,但是 *`user.aaa.email`* 就匹配不了 +> - 通配符 `#` ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 `user.#`,那么 **`user.email`** 可以匹配,**`user.aaa.email `** 也可以匹配 + +```java +/** + *

+ * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 + *

+ * + * @author yangkai.shen + * @date Created in 2018-12-29 17:03 + */ +@Slf4j +@Configuration +public class RabbitMqConfig { + + @Bean + public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { + connectionFactory.setPublisherConfirms(true); + connectionFactory.setPublisherReturns(true); + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMandatory(true); + rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); + rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message)); + return rabbitTemplate; + } + + /** + * 直接模式队列1 + */ + @Bean + public Queue directOneQueue() { + return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE); + } + + /** + * 队列2 + */ + @Bean + public Queue queueTwo() { + return new Queue(RabbitConsts.QUEUE_TWO); + } + + /** + * 队列3 + */ + @Bean + public Queue queueThree() { + return new Queue(RabbitConsts.QUEUE_THREE); + } + + /** + * 分列模式队列 + */ + @Bean + public FanoutExchange fanoutExchange() { + return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE); + } + + /** + * 分列模式绑定队列1 + * + * @param directOneQueue 绑定队列1 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(directOneQueue).to(fanoutExchange); + } + + /** + * 分列模式绑定队列2 + * + * @param queueTwo 绑定队列2 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(queueTwo).to(fanoutExchange); + } + + /** + * 主题模式队列 + *
  • 路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email
  • + *
  • 通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了
  • + *
  • 通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配
  • + */ + @Bean + public TopicExchange topicExchange() { + return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE); + } + + + /** + * 主题模式绑定分列模式 + * + * @param fanoutExchange 分列模式交换器 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) { + return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE); + } + + /** + * 主题模式绑定队列2 + * + * @param queueTwo 队列2 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) { + return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO); + } + + /** + * 主题模式绑定队列3 + * + * @param queueThree 队列3 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) { + return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE); + } + + /** + * 延迟队列 + */ + @Bean + public Queue delayQueue() { + return new Queue(RabbitConsts.DELAY_QUEUE, true); + } + + /** + * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定 + */ + @Bean + public CustomExchange delayExchange() { + Map args = Maps.newHashMap(); + args.put("x-delayed-type", "direct"); + return new CustomExchange(RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args); + } + + /** + * 延迟队列绑定自定义交换器 + * + * @param delayQueue 队列 + * @param delayExchange 延迟交换器 + */ + @Bean + public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) { + return BindingBuilder.bind(delayQueue).to(delayExchange).with(RabbitConsts.DELAY_QUEUE).noargs(); + } + +} +``` + +## 消息处理器 + +> 只展示直接队列模式的消息处理,其余模式请看源码 +> +> 需要注意:如果 `spring.rabbitmq.listener.direct.acknowledge-mode: auto`,则会自动Ack,否则需要手动Ack + +### DirectQueueOneHandler.java + +```java +/** + *

    + * 直接队列1 处理器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 + */ +@Slf4j +@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) +@Component +public class DirectQueueOneHandler { + + /** + * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack + */ + // @RabbitHandler + public void directHandlerAutoAck(MessageStruct message) { + log.info("直接队列处理器,接收消息:{}", JSONUtil.toJsonStr(message)); + } + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} +``` + +## SpringBootDemoMqRabbitmqApplicationTests.java + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoMqRabbitmqApplicationTests { + @Autowired + private RabbitTemplate rabbitTemplate; + + /** + * 测试直接模式发送 + */ + @Test + public void sendDirect() { + rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message")); + } + + /** + * 测试分列模式发送 + */ + @Test + public void sendFanout() { + rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message")); + } + + /** + * 测试主题模式发送1 + */ + @Test + public void sendTopic1() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送2 + */ + @Test + public void sendTopic2() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送3 + */ + @Test + public void sendTopic3() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message")); + } + + /** + * 测试延迟队列发送 + */ + @Test + public void sendDelay() { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 5000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 2000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 8000); + return message; + }); + } + +} +``` + +## 运行效果 + +### 直接模式 + +![image-20190107103229408](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063315-1.jpg) + +### 分列模式 + +![image-20190107103258291](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063315.jpg) + +### 主题模式 + +#### RoutingKey:`queue.#` + +![image-20190107103358744](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063316.jpg) + +#### RoutingKey:`*.queue` + +![image-20190107103429430](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063312.jpg) + +#### RoutingKey:`3.queue` + +![image-20190107103451240](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063313.jpg) + +### 延迟队列 + +![image-20190107103509943](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063314.jpg) + +## 参考 + +1. SpringQP 官方文档:https://docs.spring.io/spring-amqp/docs/2.1.0.RELEASE/reference/html/ +2. RabbitMQ 官网:http://www.rabbitmq.com/ +3. RabbitMQ延迟队列:https://www.cnblogs.com/vipstone/p/9967649.html diff --git a/demo-mq-rabbitmq/pom.xml b/demo-mq-rabbitmq/pom.xml new file mode 100644 index 0000000..fcb1f28 --- /dev/null +++ b/demo-mq-rabbitmq/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-mq-rabbitmq + 1.0.0-SNAPSHOT + jar + + demo-mq-rabbitmq + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + demo-mq-rabbitmq + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java new file mode 100644 index 0000000..2d96bb9 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.mq.rabbitmq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-29 13:58 + */ +@SpringBootApplication +public class SpringBootDemoMqRabbitmqApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMqRabbitmqApplication.class, args); + } + +} + diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java new file mode 100644 index 0000000..77addf0 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java @@ -0,0 +1,165 @@ +package com.xkcoding.mq.rabbitmq.config; + +import com.google.common.collect.Maps; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.*; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +/** + *

    + * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-29 17:03 + */ +@Slf4j +@Configuration +public class RabbitMqConfig { + + @Bean + public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { + connectionFactory.setPublisherConfirms(true); + connectionFactory.setPublisherReturns(true); + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMandatory(true); + rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); + rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message)); + return rabbitTemplate; + } + + /** + * 直接模式队列1 + */ + @Bean + public Queue directOneQueue() { + return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE); + } + + /** + * 队列2 + */ + @Bean + public Queue queueTwo() { + return new Queue(RabbitConsts.QUEUE_TWO); + } + + /** + * 队列3 + */ + @Bean + public Queue queueThree() { + return new Queue(RabbitConsts.QUEUE_THREE); + } + + /** + * 分列模式队列 + */ + @Bean + public FanoutExchange fanoutExchange() { + return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE); + } + + /** + * 分列模式绑定队列1 + * + * @param directOneQueue 绑定队列1 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(directOneQueue).to(fanoutExchange); + } + + /** + * 分列模式绑定队列2 + * + * @param queueTwo 绑定队列2 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(queueTwo).to(fanoutExchange); + } + + /** + * 主题模式队列 + *
  • 路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email
  • + *
  • 通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了
  • + *
  • 通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配
  • + */ + @Bean + public TopicExchange topicExchange() { + return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE); + } + + + /** + * 主题模式绑定分列模式 + * + * @param fanoutExchange 分列模式交换器 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) { + return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE); + } + + /** + * 主题模式绑定队列2 + * + * @param queueTwo 队列2 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) { + return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO); + } + + /** + * 主题模式绑定队列3 + * + * @param queueThree 队列3 + * @param topicExchange 主题模式交换器 + */ + @Bean + public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) { + return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE); + } + + /** + * 延迟队列 + */ + @Bean + public Queue delayQueue() { + return new Queue(RabbitConsts.DELAY_QUEUE, true); + } + + /** + * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定 + */ + @Bean + public CustomExchange delayExchange() { + Map args = Maps.newHashMap(); + args.put("x-delayed-type", "direct"); + return new CustomExchange(RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args); + } + + /** + * 延迟队列绑定自定义交换器 + * + * @param delayQueue 队列 + * @param delayExchange 延迟交换器 + */ + @Bean + public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) { + return BindingBuilder.bind(delayQueue).to(delayExchange).with(RabbitConsts.DELAY_QUEUE).noargs(); + } + +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java new file mode 100644 index 0000000..7516746 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java @@ -0,0 +1,61 @@ +package com.xkcoding.mq.rabbitmq.constants; + +/** + *

    + * RabbitMQ常量池 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-29 17:08 + */ +public interface RabbitConsts { + /** + * 直接模式1 + */ + String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; + + /** + * 队列2 + */ + String QUEUE_TWO = "queue.2"; + + /** + * 队列3 + */ + String QUEUE_THREE = "3.queue"; + + /** + * 分列模式 + */ + String FANOUT_MODE_QUEUE = "fanout.mode"; + + /** + * 主题模式 + */ + String TOPIC_MODE_QUEUE = "topic.mode"; + + /** + * 路由1 + */ + String TOPIC_ROUTING_KEY_ONE = "queue.#"; + + /** + * 路由2 + */ + String TOPIC_ROUTING_KEY_TWO = "*.queue"; + + /** + * 路由3 + */ + String TOPIC_ROUTING_KEY_THREE = "3.queue"; + + /** + * 延迟队列 + */ + String DELAY_QUEUE = "delay.queue"; + + /** + * 延迟队列交换器 + */ + String DELAY_MODE_QUEUE = "delay.mode"; +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java new file mode 100644 index 0000000..15f4b24 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java @@ -0,0 +1,45 @@ +package com.xkcoding.mq.rabbitmq.handler; + +import cn.hutool.json.JSONUtil; +import com.rabbitmq.client.Channel; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import com.xkcoding.mq.rabbitmq.message.MessageStruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + *

    + * 延迟队列处理器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-04 17:42 + */ +@Slf4j +@Component +@RabbitListener(queues = RabbitConsts.DELAY_QUEUE) +public class DelayQueueHandler { + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("延迟队列,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java new file mode 100644 index 0000000..5b7559e --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java @@ -0,0 +1,53 @@ +package com.xkcoding.mq.rabbitmq.handler; + +import cn.hutool.json.JSONUtil; +import com.rabbitmq.client.Channel; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import com.xkcoding.mq.rabbitmq.message.MessageStruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + *

    + * 直接队列1 处理器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 + */ +@Slf4j +@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) +@Component +public class DirectQueueOneHandler { + + /** + * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack + */ + // @RabbitHandler + public void directHandlerAutoAck(MessageStruct message) { + log.info("直接队列处理器,接收消息:{}", JSONUtil.toJsonStr(message)); + } + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java new file mode 100644 index 0000000..af229c1 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java @@ -0,0 +1,45 @@ +package com.xkcoding.mq.rabbitmq.handler; + +import cn.hutool.json.JSONUtil; +import com.rabbitmq.client.Channel; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import com.xkcoding.mq.rabbitmq.message.MessageStruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + *

    + * 队列2 处理器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 + */ +@Slf4j +@RabbitListener(queues = RabbitConsts.QUEUE_THREE) +@Component +public class QueueThreeHandler { + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("队列3,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java new file mode 100644 index 0000000..1369ab6 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java @@ -0,0 +1,45 @@ +package com.xkcoding.mq.rabbitmq.handler; + +import cn.hutool.json.JSONUtil; +import com.rabbitmq.client.Channel; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import com.xkcoding.mq.rabbitmq.message.MessageStruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + *

    + * 队列2 处理器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 + */ +@Slf4j +@RabbitListener(queues = RabbitConsts.QUEUE_TWO) +@Component +public class QueueTwoHandler { + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("队列2,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java new file mode 100644 index 0000000..71c1125 --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java @@ -0,0 +1,26 @@ +package com.xkcoding.mq.rabbitmq.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * 测试消息体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-29 16:22 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MessageStruct implements Serializable { + private static final long serialVersionUID = 392365881428311040L; + + private String message; +} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/resources/application.yml b/demo-mq-rabbitmq/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-mq-rabbitmq/src/main/resources/application.yml rename to demo-mq-rabbitmq/src/main/resources/application.yml diff --git a/demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java b/demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java new file mode 100644 index 0000000..27251cc --- /dev/null +++ b/demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java @@ -0,0 +1,79 @@ +package com.xkcoding.mq.rabbitmq; + +import cn.hutool.core.date.DateUtil; +import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; +import com.xkcoding.mq.rabbitmq.message.MessageStruct; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoMqRabbitmqApplicationTests { + @Autowired + private RabbitTemplate rabbitTemplate; + + /** + * 测试直接模式发送 + */ + @Test + public void sendDirect() { + rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message")); + } + + /** + * 测试分列模式发送 + */ + @Test + public void sendFanout() { + rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message")); + } + + /** + * 测试主题模式发送1 + */ + @Test + public void sendTopic1() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送2 + */ + @Test + public void sendTopic2() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送3 + */ + @Test + public void sendTopic3() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message")); + } + + /** + * 测试延迟队列发送 + */ + @Test + public void sendDelay() { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil.date()), message -> { + message.getMessageProperties().setHeader("x-delay", 5000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil.date()), message -> { + message.getMessageProperties().setHeader("x-delay", 2000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil.date()), message -> { + message.getMessageProperties().setHeader("x-delay", 8000); + return message; + }); + } + +} + diff --git a/spring-boot-demo-mq-rocketmq/.gitignore b/demo-mq-rocketmq/.gitignore similarity index 100% rename from spring-boot-demo-mq-rocketmq/.gitignore rename to demo-mq-rocketmq/.gitignore diff --git a/spring-boot-demo-mq-rocketmq/README.md b/demo-mq-rocketmq/README.md similarity index 100% rename from spring-boot-demo-mq-rocketmq/README.md rename to demo-mq-rocketmq/README.md diff --git a/demo-mq-rocketmq/pom.xml b/demo-mq-rocketmq/pom.xml new file mode 100644 index 0000000..0369f8f --- /dev/null +++ b/demo-mq-rocketmq/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + demo-mq-rocketmq + 1.0.0-SNAPSHOT + jar + + demo-mq-rocketmq + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-mq-rocketmq + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-mq-rocketmq/src/main/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplication.java b/demo-mq-rocketmq/src/main/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplication.java similarity index 100% rename from spring-boot-demo-mq-rocketmq/src/main/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplication.java rename to demo-mq-rocketmq/src/main/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplication.java diff --git a/spring-boot-demo-mq-rocketmq/src/main/resources/application.properties b/demo-mq-rocketmq/src/main/resources/application.properties similarity index 100% rename from spring-boot-demo-mq-rocketmq/src/main/resources/application.properties rename to demo-mq-rocketmq/src/main/resources/application.properties diff --git a/spring-boot-demo-mq-rocketmq/src/test/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplicationTests.java b/demo-mq-rocketmq/src/test/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplicationTests.java similarity index 100% rename from spring-boot-demo-mq-rocketmq/src/test/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplicationTests.java rename to demo-mq-rocketmq/src/test/java/com/xkcoding/mq/rocketmq/SpringBootDemoMqRocketmqApplicationTests.java diff --git a/spring-boot-demo-multi-datasource-jpa/.gitignore b/demo-multi-datasource-jpa/.gitignore similarity index 100% rename from spring-boot-demo-multi-datasource-jpa/.gitignore rename to demo-multi-datasource-jpa/.gitignore diff --git a/demo-multi-datasource-jpa/README.md b/demo-multi-datasource-jpa/README.md new file mode 100644 index 0000000..326aa93 --- /dev/null +++ b/demo-multi-datasource-jpa/README.md @@ -0,0 +1,536 @@ +# spring-boot-demo-multi-datasource-jpa + +> 此 demo 主要演示 Spring Boot 如何集成 JPA 的多数据源。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-multi-datasource-jpa + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-multi-datasource-jpa + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-multi-datasource-jpa + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## PrimaryDataSourceConfig.java + +> 主数据源配置 + +```java +/** + *

    + * JPA多数据源配置 - 主数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class PrimaryDataSourceConfig { + + /** + * 扫描spring.datasource.primary开头的配置信息 + * + * @return 数据源配置信息 + */ + @Primary + @Bean(name = "primaryDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.primary") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为primaryDataSourceProperties的bean + * @return 数据源对象 + */ + @Primary + @Bean(name = "primaryDataSource") + public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为primaryDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Primary + @Bean(name = "primaryJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} +``` + +## SecondDataSourceConfig.java + +> 从数据源配置 + +```java +/** + *

    + * JPA多数据源配置 - 次数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class SecondDataSourceConfig { + + /** + * 扫描spring.datasource.second开头的配置信息 + * + * @return 数据源配置信息 + */ + @Bean(name = "secondDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.second") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为secondDataSourceProperties的bean + * @return 数据源对象 + */ + @Bean(name = "secondDataSource") + public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为secondDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Bean(name = "secondJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} +``` + +## PrimaryJpaConfig.java + +> 主 JPA 配置 + +```java +/** + *

    + * JPA多数据源配置 - 主 JPA 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "primaryEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "primaryTransactionManager") +public class PrimaryJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; + + + /** + * 扫描spring.jpa.primary开头的配置信息 + * + * @return jpa配置信息 + */ + @Primary + @Bean(name = "primaryJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.primary") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param primaryDataSource 注入名为primaryDataSource的数据源 + * @param jpaProperties 注入名为primaryJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Primary + @Bean(name = "primaryEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(primaryDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("primaryPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Primary + @Bean(name = "primaryEntityManager") + public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Primary + @Bean(name = "primaryTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} +``` + +## SecondJpaConfig.java + +> 从 JPA 配置 + +```java +/** + *

    + * JPA多数据源配置 - 次 JPA 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "secondEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "secondTransactionManager") +public class SecondJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; + + + /** + * 扫描spring.jpa.second开头的配置信息 + * + * @return jpa配置信息 + */ + @Bean(name = "secondJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.second") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param secondDataSource 注入名为secondDataSource的数据源 + * @param jpaProperties 注入名为secondJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Bean(name = "secondEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(secondDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("secondPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Bean(name = "secondEntityManager") + public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Bean(name = "secondTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} +``` + +## application.yml + +```yaml +spring: + datasource: + primary: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: PrimaryHikariCP + max-lifetime: 60000 + connection-timeout: 30000 + second: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SecondHikariCP + max-lifetime: 60000 + connection-timeout: 30000 + jpa: + primary: + show-sql: true + generate-ddl: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57InnoDBDialect + open-in-view: true + second: + show-sql: true + generate-ddl: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57InnoDBDialect + open-in-view: true +logging: + level: + com.xkcoding: debug + org.hibernate.SQL: debug + org.hibernate.type: trace +``` + +## SpringBootDemoMultiDatasourceJpaApplicationTests.java + +```java +package com.xkcoding.multi.datasource.jpa; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Snowflake; +import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; +import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; +import com.xkcoding.multi.datasource.jpa.repository.primary.PrimaryMultiTableRepository; +import com.xkcoding.multi.datasource.jpa.repository.second.SecondMultiTableRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class SpringBootDemoMultiDatasourceJpaApplicationTests { + @Autowired + private PrimaryMultiTableRepository primaryRepo; + @Autowired + private SecondMultiTableRepository secondRepo; + @Autowired + private Snowflake snowflake; + + @Test + public void testInsert() { + PrimaryMultiTable primary = new PrimaryMultiTable(snowflake.nextId(),"测试名称-1"); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + } + + @Test + public void testUpdate() { + primaryRepo.findAll().forEach(primary -> { + primary.setName("修改后的"+primary.getName()); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + }); + } + + @Test + public void testDelete() { + primaryRepo.deleteAll(); + + secondRepo.deleteAll(); + } + + @Test + public void testSelect() { + List primary = primaryRepo.findAll(); + log.info("【primary】= {}", primary); + + List second = secondRepo.findAll(); + log.info("【second】= {}", second); + } + +} +``` + +## 目录结构 + +``` +. +├── README.md +├── pom.xml +├── spring-boot-demo-multi-datasource-jpa.iml +├── src +│   ├── main +│   │   ├── java +│   │   │   └── com.xkcoding.multi.datasource.jpa +│   │   │   ├── SpringBootDemoMultiDatasourceJpaApplication.java +│   │   │   ├── config +│   │   │   │   ├── PrimaryDataSourceConfig.java +│   │   │   │   ├── PrimaryJpaConfig.java +│   │   │   │   ├── SecondDataSourceConfig.java +│   │   │   │   ├── SecondJpaConfig.java +│   │   │   │   └── SnowflakeConfig.java +│   │   │   ├── entity +│   │   │   │   ├── primary +│   │   │   │   │   └── PrimaryMultiTable.java +│   │   │   │   └── second +│   │   │   │   └── SecondMultiTable.java +│   │   │   └── repository +│   │   │   ├── primary +│   │   │   │   └── PrimaryMultiTableRepository.java +│   │   │   └── second +│   │   │   └── SecondMultiTableRepository.java +│   │   └── resources +│   │   └── application.yml +│   └── test +│   └── java +│   └── com.xkcoding.multi.datasource.jpa +│   └── SpringBootDemoMultiDatasourceJpaApplicationTests.java +└── target +``` + +## 参考 + +1. https://www.jianshu.com/p/34730e595a8c +2. https://blog.csdn.net/anxpp/article/details/52274120 diff --git a/demo-multi-datasource-jpa/pom.xml b/demo-multi-datasource-jpa/pom.xml new file mode 100644 index 0000000..a14af77 --- /dev/null +++ b/demo-multi-datasource-jpa/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + demo-multi-datasource-jpa + 1.0.0-SNAPSHOT + jar + + demo-multi-datasource-jpa + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-multi-datasource-jpa + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java new file mode 100644 index 0000000..e5451c2 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.multi.datasource.jpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-16 17:34 + */ +@SpringBootApplication +public class SpringBootDemoMultiDatasourceJpaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceJpaApplication.class, args); + } + +} + diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java new file mode 100644 index 0000000..fcfcb25 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java @@ -0,0 +1,60 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; + +/** + *

    + * JPA多数据源配置 - 主数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class PrimaryDataSourceConfig { + + /** + * 扫描spring.datasource.primary开头的配置信息 + * + * @return 数据源配置信息 + */ + @Primary + @Bean(name = "primaryDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.primary") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为primaryDataSourceProperties的bean + * @return 数据源对象 + */ + @Primary + @Bean(name = "primaryDataSource") + public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为primaryDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Primary + @Bean(name = "primaryJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java new file mode 100644 index 0000000..39f93e6 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java @@ -0,0 +1,100 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +/** + *

    + * JPA多数据源配置 - 主 JPA 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "primaryEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "primaryTransactionManager") +public class PrimaryJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; + + + /** + * 扫描spring.jpa.primary开头的配置信息 + * + * @return jpa配置信息 + */ + @Primary + @Bean(name = "primaryJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.primary") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param primaryDataSource 注入名为primaryDataSource的数据源 + * @param jpaProperties 注入名为primaryJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Primary + @Bean(name = "primaryEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(primaryDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("primaryPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Primary + @Bean(name = "primaryEntityManager") + public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Primary + @Bean(name = "primaryTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java new file mode 100644 index 0000000..49b7746 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java @@ -0,0 +1,56 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; + +/** + *

    + * JPA多数据源配置 - 次数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class SecondDataSourceConfig { + + /** + * 扫描spring.datasource.second开头的配置信息 + * + * @return 数据源配置信息 + */ + @Bean(name = "secondDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.second") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为secondDataSourceProperties的bean + * @return 数据源对象 + */ + @Bean(name = "secondDataSource") + public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为secondDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Bean(name = "secondJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java new file mode 100644 index 0000000..ebbc349 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java @@ -0,0 +1,95 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +/** + *

    + * JPA多数据源配置 - 次 JPA 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "secondEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "secondTransactionManager") +public class SecondJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; + + + /** + * 扫描spring.jpa.second开头的配置信息 + * + * @return jpa配置信息 + */ + @Bean(name = "secondJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.second") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param secondDataSource 注入名为secondDataSource的数据源 + * @param jpaProperties 注入名为secondJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Bean(name = "secondEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(secondDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("secondPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Bean(name = "secondEntityManager") + public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Bean(name = "secondTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java new file mode 100644 index 0000000..2508fb9 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java @@ -0,0 +1,22 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

    + * 雪花算法生成器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-18 15:50 + */ +@Configuration +public class SnowflakeConfig { + @Bean + public Snowflake snowflake() { + return IdUtil.createSnowflake(1, 1); + } +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java new file mode 100644 index 0000000..4904e69 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java @@ -0,0 +1,37 @@ +package com.xkcoding.multi.datasource.jpa.entity.primary; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *

    + * 多数据源测试表 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-18 10:06 + */ +@Data +@Entity +@Table(name = "multi_table") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PrimaryMultiTable { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名称 + */ + private String name; +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java new file mode 100644 index 0000000..3756681 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java @@ -0,0 +1,37 @@ +package com.xkcoding.multi.datasource.jpa.entity.second; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *

    + * 多数据源测试表 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-18 10:06 + */ +@Data +@Entity +@Table(name = "multi_table") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SecondMultiTable { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名称 + */ + private String name; +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java new file mode 100644 index 0000000..91cd78b --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java @@ -0,0 +1,17 @@ +package com.xkcoding.multi.datasource.jpa.repository.primary; + +import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + *

    + * 多数据源测试 repo + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-18 10:11 + */ +@Repository +public interface PrimaryMultiTableRepository extends JpaRepository { +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java new file mode 100644 index 0000000..0fc6ba8 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java @@ -0,0 +1,17 @@ +package com.xkcoding.multi.datasource.jpa.repository.second; + +import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + *

    + * 多数据源测试 repo + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-18 10:11 + */ +@Repository +public interface SecondMultiTableRepository extends JpaRepository { +} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/resources/application.yml b/demo-multi-datasource-jpa/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-multi-datasource-jpa/src/main/resources/application.yml rename to demo-multi-datasource-jpa/src/main/resources/application.yml diff --git a/demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java b/demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java new file mode 100644 index 0000000..9e6bc4d --- /dev/null +++ b/demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java @@ -0,0 +1,68 @@ +package com.xkcoding.multi.datasource.jpa; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Snowflake; +import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; +import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; +import com.xkcoding.multi.datasource.jpa.repository.primary.PrimaryMultiTableRepository; +import com.xkcoding.multi.datasource.jpa.repository.second.SecondMultiTableRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class SpringBootDemoMultiDatasourceJpaApplicationTests { + @Autowired + private PrimaryMultiTableRepository primaryRepo; + @Autowired + private SecondMultiTableRepository secondRepo; + @Autowired + private Snowflake snowflake; + + @Test + public void testInsert() { + PrimaryMultiTable primary = new PrimaryMultiTable(snowflake.nextId(), "测试名称-1"); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + } + + @Test + public void testUpdate() { + primaryRepo.findAll().forEach(primary -> { + primary.setName("修改后的" + primary.getName()); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + }); + } + + @Test + public void testDelete() { + primaryRepo.deleteAll(); + + secondRepo.deleteAll(); + } + + @Test + public void testSelect() { + List primary = primaryRepo.findAll(); + log.info("【primary】= {}", primary); + + List second = secondRepo.findAll(); + log.info("【second】= {}", second); + } + +} + diff --git a/spring-boot-demo-multi-datasource-mybatis/.gitignore b/demo-multi-datasource-mybatis/.gitignore similarity index 100% rename from spring-boot-demo-multi-datasource-mybatis/.gitignore rename to demo-multi-datasource-mybatis/.gitignore diff --git a/demo-multi-datasource-mybatis/README.md b/demo-multi-datasource-mybatis/README.md new file mode 100644 index 0000000..0bacd48 --- /dev/null +++ b/demo-multi-datasource-mybatis/README.md @@ -0,0 +1,352 @@ +# spring-boot-demo-multi-datasource-mybatis + +> 此 demo 主要演示了 Spring Boot 如何集成 Mybatis 的多数据源。可以自己基于AOP实现多数据源,这里基于 Mybatis-Plus 提供的一个优雅的开源的解决方案来实现。 + +## 准备工作 + +准备两个数据源,分别执行如下建表语句 + +```mysql +DROP TABLE IF EXISTS `multi_user`; +CREATE TABLE `multi_user`( + `id` bigint(64) NOT NULL, + `name` varchar(50) DEFAULT NULL, + `age` int(30) DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + CHARACTER SET = utf8 + COLLATE = utf8_general_ci; +``` + +## 导入依赖 + +```xml + + + 4.0.0 + + spring-boot-demo-multi-datasource-mybatis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-multi-datasource-mybatis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 2.5.0 + + + + com.baomidou + mybatis-plus-boot-starter + 3.0.7.1 + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + spring-boot-demo-multi-datasource-mybatis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## 准备实体类 + +`User.java` + +> 1. @Data / @NoArgsConstructor / @AllArgsConstructor / @Builder 都是 lombok 注解 +> 2. @TableName("multi_user") 是 Mybatis-Plus 注解,主要是当实体类名字和表名不满足 **驼峰和下划线互转** 的格式时,用于表示数据库表名 +> 3. @TableId(type = IdType.ID_WORKER) 是 Mybatis-Plus 注解,主要是指定主键类型,这里我使用的是 Mybatis-Plus 基于 twitter 提供的 雪花算法 + +```java +/** + *

    + * User实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@Data +@TableName("multi_user") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements Serializable { + private static final long serialVersionUID = -1923859222295750467L; + + /** + * 主键 + */ + @TableId(type = IdType.ID_WORKER) + private Long id; + + /** + * 姓名 + */ + private String name; + + /** + * 年龄 + */ + private Integer age; +} +``` + +## 数据访问层 + +`UserMapper.java` + +> 不需要建对应的xml,只需要继承 BaseMapper 就拥有了大部分单表操作的方法了。 + +```java +/** + *

    + * 数据访问层 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:28 + */ +public interface UserMapper extends BaseMapper { +} +``` + +## 数据服务层 + +### 接口 + +`UserService.java` + +```java +/** + *

    + * 数据服务层 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:31 + */ +public interface UserService extends IService { + + /** + * 添加 User + * + * @param user 用户 + */ + void addUser(User user); +} +``` + +### 实现 + +`UserServiceImpl.java` + +> 1. @DS: 注解在类上或方法上来切换数据源,方法上的@DS优先级大于类上的@DS +> 2. baseMapper: mapper 对象,即`UserMapper`,可获得CRUD功能 +> 3. 默认走从库: `@DS(value = "slave")`在类上,默认走从库,除非在方法在添加`@DS(value = "master")`才走主库 + +```java +/** + *

    + * 数据服务层 实现 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:37 + */ +@Service +@DS("slave") +public class UserServiceImpl extends ServiceImpl implements UserService { + + /** + * 类上 {@code @DS("slave")} 代表默认从库,在方法上写 {@code @DS("master")} 代表默认主库 + * + * @param user 用户 + */ + @DS("master") + @Override + public void addUser(User user) { + baseMapper.insert(user); + } +} +``` + +## 启动类 + +`SpringBootDemoMultiDatasourceMybatisApplication.java` + +> 启动类上方需要使用@MapperScan扫描 mapper 类所在的包 + +```java +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@SpringBootApplication +@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") +public class SpringBootDemoMultiDatasourceMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); + } + +} +``` + +## 配置文件 + +`application.yml` + +```yaml +spring: + datasource: + dynamic: + datasource: + master: + username: root + password: root + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + driver-class-name: com.mysql.cj.jdbc.Driver + slave: + username: root + password: root + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + driver-class-name: com.mysql.cj.jdbc.Driver + mp-enabled: true +logging: + level: + com.xkcoding.multi.datasource.mybatis: debug +``` + +## 测试类 + +```java +/** + *

    + * 测试主从数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:45 + */ +@Slf4j +public class UserServiceImplTest extends SpringBootDemoMultiDatasourceMybatisApplicationTests { + @Autowired + private UserService userService; + + /** + * 主从库添加 + */ + @Test + public void addUser() { + User userMaster = User.builder().name("主库添加").age(20).build(); + userService.addUser(userMaster); + + User userSlave = User.builder().name("从库添加").age(20).build(); + userService.save(userSlave); + } + + /** + * 从库查询 + */ + @Test + public void testListUser() { + List list = userService.list(new QueryWrapper<>()); + log.info("【list】= {}", JSONUtil.toJsonStr(list)); + } +} +``` + +### 测试结果 + +主从数据源加载成功 + +```java +2019-01-21 14:55:41.096 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : master - Starting... +2019-01-21 14:55:41.307 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : master - Start completed. +2019-01-21 14:55:41.308 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : slave - Starting... +2019-01-21 14:55:41.312 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : slave - Start completed. +2019-01-21 14:55:41.312 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 初始共加载 2 个数据源 +2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 动态数据源-加载 slave 成功 +2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 动态数据源-加载 master 成功 +2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 当前的默认数据源是单数据源,数据源名为 master + _ _ |_ _ _|_. ___ _ | _ +| | |\/|_)(_| | |_\ |_)||_|_\ + / | + 3.0.7.1 +``` + +**主**库 **建议** 只执行 **INSERT** **UPDATE** **DELETE** 操作 + +![image-20190121153211509](http://static.xkcoding.com/spring-boot-demo/multi-datasource/mybatis/063506.jpg) + +**从**库 **建议** 只执行 **SELECT** 操作 + +![image-20190121152825859](http://static.xkcoding.com/spring-boot-demo/multi-datasource/mybatis/063505.jpg) + +> 生产环境需要搭建 **主从复制** + +## 参考 + +1. Mybatis-Plus 多数据源文档:https://mybatis.plus/guide/dynamic-datasource.html +2. Mybatis-Plus 多数据源集成官方 demo:https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter/tree/master/samples diff --git a/demo-multi-datasource-mybatis/pom.xml b/demo-multi-datasource-mybatis/pom.xml new file mode 100644 index 0000000..f4502ba --- /dev/null +++ b/demo-multi-datasource-mybatis/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + demo-multi-datasource-mybatis + 1.0.0-SNAPSHOT + jar + + demo-multi-datasource-mybatis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 2.5.0 + + + + com.baomidou + mybatis-plus-boot-starter + 3.1.0 + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + demo-multi-datasource-mybatis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-multi-datasource-mybatis/sql/db.sql b/demo-multi-datasource-mybatis/sql/db.sql similarity index 100% rename from spring-boot-demo-multi-datasource-mybatis/sql/db.sql rename to demo-multi-datasource-mybatis/sql/db.sql diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java new file mode 100644 index 0000000..bdeb42b --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java @@ -0,0 +1,24 @@ +package com.xkcoding.multi.datasource.mybatis; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@SpringBootApplication +@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") +public class SpringBootDemoMultiDatasourceMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); + } + +} + diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java new file mode 100644 index 0000000..3e8999c --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java @@ -0,0 +1,15 @@ +package com.xkcoding.multi.datasource.mybatis.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.multi.datasource.mybatis.model.User; + +/** + *

    + * 数据访问层 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:28 + */ +public interface UserMapper extends BaseMapper { +} diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java new file mode 100644 index 0000000..6790049 --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java @@ -0,0 +1,44 @@ +package com.xkcoding.multi.datasource.mybatis.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * User实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@Data +@TableName("multi_user") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements Serializable { + private static final long serialVersionUID = -1923859222295750467L; + + /** + * 主键 + */ + @TableId(type = IdType.ID_WORKER) + private Long id; + + /** + * 姓名 + */ + private String name; + + /** + * 年龄 + */ + private Integer age; +} diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java new file mode 100644 index 0000000..8e80563 --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java @@ -0,0 +1,22 @@ +package com.xkcoding.multi.datasource.mybatis.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.xkcoding.multi.datasource.mybatis.model.User; + +/** + *

    + * 数据服务层 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:31 + */ +public interface UserService extends IService { + + /** + * 添加 User + * + * @param user 用户 + */ + void addUser(User user); +} diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..65a4f5a --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java @@ -0,0 +1,32 @@ +package com.xkcoding.multi.datasource.mybatis.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xkcoding.multi.datasource.mybatis.mapper.UserMapper; +import com.xkcoding.multi.datasource.mybatis.model.User; +import com.xkcoding.multi.datasource.mybatis.service.UserService; +import org.springframework.stereotype.Service; + +/** + *

    + * 数据服务层 实现 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:37 + */ +@Service +@DS("slave") +public class UserServiceImpl extends ServiceImpl implements UserService { + + /** + * 类上 {@code @DS("slave")} 代表默认从库,在方法上写 {@code @DS("master")} 代表默认主库 + * + * @param user 用户 + */ + @DS("master") + @Override + public void addUser(User user) { + baseMapper.insert(user); + } +} diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/resources/application.yml b/demo-multi-datasource-mybatis/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-multi-datasource-mybatis/src/main/resources/application.yml rename to demo-multi-datasource-mybatis/src/main/resources/application.yml diff --git a/spring-boot-demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplicationTests.java b/demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplicationTests.java similarity index 100% rename from spring-boot-demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplicationTests.java rename to demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplicationTests.java diff --git a/demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java b/demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java new file mode 100644 index 0000000..fe11b6d --- /dev/null +++ b/demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java @@ -0,0 +1,47 @@ +package com.xkcoding.multi.datasource.mybatis.service.impl; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.xkcoding.multi.datasource.mybatis.SpringBootDemoMultiDatasourceMybatisApplicationTests; +import com.xkcoding.multi.datasource.mybatis.model.User; +import com.xkcoding.multi.datasource.mybatis.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + *

    + * 测试主从数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-21 14:45 + */ +@Slf4j +public class UserServiceImplTest extends SpringBootDemoMultiDatasourceMybatisApplicationTests { + @Autowired + private UserService userService; + + /** + * 主从库添加 + */ + @Test + public void addUser() { + User userMaster = User.builder().name("主库添加").age(20).build(); + userService.addUser(userMaster); + + User userSlave = User.builder().name("从库添加").age(20).build(); + userService.save(userSlave); + } + + /** + * 从库查询 + */ + @Test + public void testListUser() { + List list = userService.list(new QueryWrapper<>()); + log.info("【list】= {}", JSONUtil.toJsonStr(list)); + } +} diff --git a/spring-boot-demo-neo4j/.gitignore b/demo-neo4j/.gitignore similarity index 100% rename from spring-boot-demo-neo4j/.gitignore rename to demo-neo4j/.gitignore diff --git a/demo-neo4j/README.md b/demo-neo4j/README.md new file mode 100644 index 0000000..0321a99 --- /dev/null +++ b/demo-neo4j/README.md @@ -0,0 +1,319 @@ +# spring-boot-demo-neo4j + +> 此 demo 主要演示了 Spring Boot 如何集成Neo4j操作图数据库,实现一个校园人物关系网。 + +## 注意 + +作者编写本demo时,Neo4j 版本为 `3.5.0`,使用 docker 运行,下面是所有步骤: + +1. 下载镜像:`docker pull neo4j:3.5.0` +2. 运行容器:`docker run -d -p 7474:7474 -p 7687:7687 --name neo4j-3.5.0 neo4j:3.5.0` +3. 停止容器:`docker stop neo4j-3.5.0` +4. 启动容器:`docker start neo4j-3.5.0` +5. 浏览器 http://localhost:7474/ 访问 neo4j 管理后台,初始账号/密码 neo4j/neo4j,会要求修改初始化密码,我们修改为 neo4j/admin + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-neo4j + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-neo4j + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + spring-boot-demo-neo4j + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + data: + neo4j: + uri: bolt://localhost + username: neo4j + password: admin + open-in-view: false +``` + +## CustomIdStrategy.java + +```java +/** + *

    + * 自定义主键策略 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:40 + */ +public class CustomIdStrategy implements IdStrategy { + @Override + public Object generateId(Object o) { + return IdUtil.fastUUID(); + } +} +``` + +## 部分Model代码 + +### Student.java + +```java +/** + *

    + * 学生节点 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:38 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Student { + /** + * 主键,自定义主键策略,使用UUID生成 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 学生姓名 + */ + @NonNull + private String name; + + /** + * 学生选的所有课程 + */ + @Relationship(NeoConsts.R_LESSON_OF_STUDENT) + @NonNull + private List lessons; + + /** + * 学生所在班级 + */ + @Relationship(NeoConsts.R_STUDENT_OF_CLASS) + @NonNull + private Class clazz; + +} +``` + +## 部分Repository代码 + +### StudentRepository.java + +```java +/** + *

    + * 学生节点Repository + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface StudentRepository extends Neo4jRepository { + /** + * 根据名称查找学生 + * + * @param name 姓名 + * @param depth 深度 + * @return 学生信息 + */ + Optional findByName(String name, @Depth int depth); + + /** + * 根据班级查询班级人数 + * + * @param className 班级名称 + * @return 班级人数 + */ + @Query("MATCH (s:Student)-[r:R_STUDENT_OF_CLASS]->(c:Class{name:{className}}) return count(s)") + Long countByClassName(@Param("className") String className); + + + /** + * 查询满足 (学生)-[选课关系]-(课程)-[选课关系]-(学生) 关系的 同学 + * + * @return 返回同学关系 + */ + @Query("match (s:Student)-[:R_LESSON_OF_STUDENT]->(l:Lesson)<-[:R_LESSON_OF_STUDENT]-(:Student) with l.name as lessonName,collect(distinct s) as students return lessonName,students") + List findByClassmateGroupByLesson(); + + /** + * 查询师生关系,(学生)-[班级学生关系]-(班级)-[班主任关系]-(教师) + * + * @return 返回师生关系 + */ + @Query("match (s:Student)-[:R_STUDENT_OF_CLASS]->(:Class)-[:R_BOSS_OF_CLASS]->(t:Teacher) with t.name as teacherName,collect(distinct s) as students return teacherName,students") + List findTeacherStudentByClass(); + + /** + * 查询师生关系,(学生)-[选课关系]-(课程)-[任教老师关系]-(教师) + * + * @return 返回师生关系 + */ + @Query("match ((s:Student)-[:R_LESSON_OF_STUDENT]->(:Lesson)-[:R_TEACHER_OF_LESSON]->(t:Teacher))with t.name as teacherName,collect(distinct s) as students return teacherName,students") + List findTeacherStudentByLesson(); +} +``` + +## Neo4jTest.java + +```java +/** + *

    + * 测试Neo4j + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:17 + */ +@Slf4j +public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { + @Autowired + private NeoService neoService; + + /** + * 测试保存 + */ + @Test + public void testSave() { + neoService.initData(); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + neoService.delete(); + } + + /** + * 测试查询 鸣人 学了哪些课程 + */ + @Test + public void testFindLessonsByStudent() { + // 深度为1,则课程的任教老师的属性为null + // 深度为2,则会把课程的任教老师的属性赋值 + List lessons = neoService.findLessonsFromStudent("漩涡鸣人", 2); + + lessons.forEach(lesson -> log.info("【lesson】= {}", JSONUtil.toJsonStr(lesson))); + } + + /** + * 测试查询班级人数 + */ + @Test + public void testCountStudent() { + Long all = neoService.studentCount(null); + log.info("【全校人数】= {}", all); + Long seven = neoService.studentCount("第七班"); + log.info("【第七班人数】= {}", seven); + } + + /** + * 测试根据课程查询同学关系 + */ + @Test + public void testFindClassmates() { + Map> classmates = neoService.findClassmatesGroupByLesson(); + classmates.forEach((k, v) -> log.info("因为一起上了【{}】这门课,成为同学关系的有:{}", k, JSONUtil.toJsonStr(v.stream() + .map(Student::getName) + .collect(Collectors.toList())))); + } + + /** + * 查询所有师生关系,包括班主任/学生,任课老师/学生 + */ + @Test + public void testFindTeacherStudent() { + Map> teacherStudent = neoService.findTeacherStudent(); + teacherStudent.forEach((k, v) -> log.info("【{}】教的学生有 {}", k, JSONUtil.toJsonStr(v.stream() + .map(Student::getName) + .collect(Collectors.toList())))); + } +} +``` + +## 截图 + +运行测试类之后,可以通过访问 http://localhost:7474 ,查看neo里所有节点和关系 + +![image-20181225150513101](http://static.xkcoding.com/spring-boot-demo/neo4j/063605.jpg) + + + +## 参考 + +- spring-data-neo4j 官方文档:https://docs.spring.io/spring-data/neo4j/docs/5.1.2.RELEASE/reference/html/ +- neo4j 官方文档:https://neo4j.com/docs/getting-started/3.5/ diff --git a/demo-neo4j/pom.xml b/demo-neo4j/pom.xml new file mode 100644 index 0000000..bf1b63f --- /dev/null +++ b/demo-neo4j/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-neo4j + 1.0.0-SNAPSHOT + jar + + demo-neo4j + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + + demo-neo4j + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java new file mode 100644 index 0000000..45bc870 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.neo4j; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-22 23:50 + */ +@SpringBootApplication +public class SpringBootDemoNeo4jApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoNeo4jApplication.class, args); + } +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java new file mode 100644 index 0000000..511236b --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java @@ -0,0 +1,19 @@ +package com.xkcoding.neo4j.config; + +import cn.hutool.core.util.IdUtil; +import org.neo4j.ogm.id.IdStrategy; + +/** + *

    + * 自定义主键策略 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:40 + */ +public class CustomIdStrategy implements IdStrategy { + @Override + public Object generateId(Object o) { + return IdUtil.fastUUID(); + } +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java new file mode 100644 index 0000000..b420272 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java @@ -0,0 +1,32 @@ +package com.xkcoding.neo4j.constants; + +/** + *

    + * 常量池 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:45 + */ +public interface NeoConsts { + /** + * 关系:班级拥有的学生 + */ + String R_STUDENT_OF_CLASS = "R_STUDENT_OF_CLASS"; + + /** + * 关系:班级的班主任 + */ + String R_BOSS_OF_CLASS = "R_BOSS_OF_CLASS"; + + /** + * 关系:课程的老师 + */ + String R_TEACHER_OF_LESSON = "R_TEACHER_OF_LESSON"; + + /** + * 关系:学生选的课 + */ + String R_LESSON_OF_STUDENT = "R_LESSON_OF_STUDENT"; + +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java new file mode 100644 index 0000000..faf5835 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java @@ -0,0 +1,45 @@ +package com.xkcoding.neo4j.model; + +import com.xkcoding.neo4j.config.CustomIdStrategy; +import com.xkcoding.neo4j.constants.NeoConsts; +import lombok.*; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + *

    + * 班级节点 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:44 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Class { + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 班级名称 + */ + @NonNull + private String name; + + /** + * 班级的班主任 + */ + @Relationship(NeoConsts.R_BOSS_OF_CLASS) + @NonNull + private Teacher boss; +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java new file mode 100644 index 0000000..6fa9f43 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java @@ -0,0 +1,45 @@ +package com.xkcoding.neo4j.model; + +import com.xkcoding.neo4j.config.CustomIdStrategy; +import com.xkcoding.neo4j.constants.NeoConsts; +import lombok.*; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + *

    + * 课程节点 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:55 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Lesson { + /** + * 主键,自定义主键策略,使用UUID生成 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 课程名称 + */ + @NonNull + private String name; + + /** + * 任教老师 + */ + @Relationship(NeoConsts.R_TEACHER_OF_LESSON) + @NonNull + private Teacher teacher; +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java new file mode 100644 index 0000000..56dd4f7 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java @@ -0,0 +1,55 @@ +package com.xkcoding.neo4j.model; + +import com.xkcoding.neo4j.config.CustomIdStrategy; +import com.xkcoding.neo4j.constants.NeoConsts; +import lombok.*; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +import java.util.List; + +/** + *

    + * 学生节点 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:38 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Student { + /** + * 主键,自定义主键策略,使用UUID生成 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 学生姓名 + */ + @NonNull + private String name; + + /** + * 学生选的所有课程 + */ + @Relationship(NeoConsts.R_LESSON_OF_STUDENT) + @NonNull + private List lessons; + + /** + * 学生所在班级 + */ + @Relationship(NeoConsts.R_STUDENT_OF_CLASS) + @NonNull + private Class clazz; + +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java new file mode 100644 index 0000000..62106ed --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java @@ -0,0 +1,36 @@ +package com.xkcoding.neo4j.model; + +import com.xkcoding.neo4j.config.CustomIdStrategy; +import lombok.*; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; + +/** + *

    + * 教师节点 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 14:54 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Teacher { + /** + * 主键,自定义主键策略,使用UUID生成 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 教师姓名 + */ + @NonNull + private String name; +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java new file mode 100644 index 0000000..453d11d --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java @@ -0,0 +1,29 @@ +package com.xkcoding.neo4j.payload; + +import com.xkcoding.neo4j.model.Student; +import lombok.Data; +import org.springframework.data.neo4j.annotation.QueryResult; + +import java.util.List; + +/** + *

    + * 按照课程分组的同学关系 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 19:18 + */ +@Data +@QueryResult +public class ClassmateInfoGroupByLesson { + /** + * 课程名称 + */ + private String lessonName; + + /** + * 学生信息 + */ + private List students; +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java new file mode 100644 index 0000000..d70fcf2 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java @@ -0,0 +1,29 @@ +package com.xkcoding.neo4j.payload; + +import com.xkcoding.neo4j.model.Student; +import lombok.Data; +import org.springframework.data.neo4j.annotation.QueryResult; + +import java.util.List; + +/** + *

    + * 师生关系 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 19:18 + */ +@Data +@QueryResult +public class TeacherStudent { + /** + * 教师姓名 + */ + private String teacherName; + + /** + * 学生信息 + */ + private List students; +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java new file mode 100644 index 0000000..ef85979 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java @@ -0,0 +1,24 @@ +package com.xkcoding.neo4j.repository; + +import com.xkcoding.neo4j.model.Class; +import org.springframework.data.neo4j.repository.Neo4jRepository; + +import java.util.Optional; + +/** + *

    + * 班级节点Repository + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface ClassRepository extends Neo4jRepository { + /** + * 根据班级名称查询班级信息 + * + * @param name 班级名称 + * @return 班级信息 + */ + Optional findByName(String name); +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java new file mode 100644 index 0000000..fcf010c --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java @@ -0,0 +1,15 @@ +package com.xkcoding.neo4j.repository; + +import com.xkcoding.neo4j.model.Lesson; +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + *

    + * 课程节点Repository + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface LessonRepository extends Neo4jRepository { +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java new file mode 100644 index 0000000..a5037c9 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java @@ -0,0 +1,65 @@ +package com.xkcoding.neo4j.repository; + +import com.xkcoding.neo4j.model.Student; +import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; +import com.xkcoding.neo4j.payload.TeacherStudent; +import org.springframework.data.neo4j.annotation.Depth; +import org.springframework.data.neo4j.annotation.Query; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + *

    + * 学生节点Repository + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface StudentRepository extends Neo4jRepository { + /** + * 根据名称查找学生 + * + * @param name 姓名 + * @param depth 深度 + * @return 学生信息 + */ + Optional findByName(String name, @Depth int depth); + + /** + * 根据班级查询班级人数 + * + * @param className 班级名称 + * @return 班级人数 + */ + @Query("MATCH (s:Student)-[r:R_STUDENT_OF_CLASS]->(c:Class{name:{className}}) return count(s)") + Long countByClassName(@Param("className") String className); + + + /** + * 查询满足 (学生)-[选课关系]-(课程)-[选课关系]-(学生) 关系的 同学 + * + * @return 返回同学关系 + */ + @Query("match (s:Student)-[:R_LESSON_OF_STUDENT]->(l:Lesson)<-[:R_LESSON_OF_STUDENT]-(:Student) with l.name as lessonName,collect(distinct s) as students return lessonName,students") + List findByClassmateGroupByLesson(); + + /** + * 查询师生关系,(学生)-[班级学生关系]-(班级)-[班主任关系]-(教师) + * + * @return 返回师生关系 + */ + @Query("match (s:Student)-[:R_STUDENT_OF_CLASS]->(:Class)-[:R_BOSS_OF_CLASS]->(t:Teacher) with t.name as teacherName,collect(distinct s) as students return teacherName,students") + List findTeacherStudentByClass(); + + /** + * 查询师生关系,(学生)-[选课关系]-(课程)-[任教老师关系]-(教师) + * + * @return 返回师生关系 + */ + @Query("match ((s:Student)-[:R_LESSON_OF_STUDENT]->(:Lesson)-[:R_TEACHER_OF_LESSON]->(t:Teacher))with t.name as teacherName,collect(distinct s) as students return teacherName,students") + List findTeacherStudentByLesson(); +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java new file mode 100644 index 0000000..f7d2d64 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java @@ -0,0 +1,15 @@ +package com.xkcoding.neo4j.repository; + +import com.xkcoding.neo4j.model.Teacher; +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + *

    + * 教师节点Repository + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface TeacherRepository extends Neo4jRepository { +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java new file mode 100644 index 0000000..01c84c7 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java @@ -0,0 +1,177 @@ +package com.xkcoding.neo4j.service; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.xkcoding.neo4j.model.Class; +import com.xkcoding.neo4j.model.Lesson; +import com.xkcoding.neo4j.model.Student; +import com.xkcoding.neo4j.model.Teacher; +import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; +import com.xkcoding.neo4j.payload.TeacherStudent; +import com.xkcoding.neo4j.repository.ClassRepository; +import com.xkcoding.neo4j.repository.LessonRepository; +import com.xkcoding.neo4j.repository.StudentRepository; +import com.xkcoding.neo4j.repository.TeacherRepository; +import org.neo4j.ogm.session.Session; +import org.neo4j.ogm.session.SessionFactory; +import org.neo4j.ogm.transaction.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

    + * NeoService + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:19 + */ +@Service +public class NeoService { + @Autowired + private ClassRepository classRepo; + + @Autowired + private LessonRepository lessonRepo; + + @Autowired + private StudentRepository studentRepo; + + @Autowired + private TeacherRepository teacherRepo; + + @Autowired + private SessionFactory sessionFactory; + + /** + * 初始化数据 + */ + @Transactional + public void initData() { + // 初始化老师 + Teacher akai = Teacher.of("迈特凯"); + Teacher kakaxi = Teacher.of("旗木卡卡西"); + Teacher zilaiye = Teacher.of("自来也"); + Teacher gangshou = Teacher.of("纲手"); + Teacher dashewan = Teacher.of("大蛇丸"); + teacherRepo.save(akai); + teacherRepo.save(kakaxi); + teacherRepo.save(zilaiye); + teacherRepo.save(gangshou); + teacherRepo.save(dashewan); + + // 初始化课程 + Lesson tishu = Lesson.of("体术", akai); + Lesson huanshu = Lesson.of("幻术", kakaxi); + Lesson shoulijian = Lesson.of("手里剑", kakaxi); + Lesson luoxuanwan = Lesson.of("螺旋丸", zilaiye); + Lesson xianshu = Lesson.of("仙术", zilaiye); + Lesson yiliao = Lesson.of("医疗", gangshou); + Lesson zhouyin = Lesson.of("咒印", dashewan); + lessonRepo.save(tishu); + lessonRepo.save(huanshu); + lessonRepo.save(shoulijian); + lessonRepo.save(luoxuanwan); + lessonRepo.save(xianshu); + lessonRepo.save(yiliao); + lessonRepo.save(zhouyin); + + // 初始化班级 + Class three = Class.of("第三班", akai); + Class seven = Class.of("第七班", kakaxi); + classRepo.save(three); + classRepo.save(seven); + + // 初始化学生 + List threeClass = Lists.newArrayList(Student.of("漩涡鸣人", Lists.newArrayList(tishu, shoulijian, luoxuanwan, xianshu), seven), Student.of("宇智波佐助", Lists.newArrayList(huanshu, zhouyin, shoulijian), seven), Student.of("春野樱", Lists.newArrayList(tishu, yiliao, shoulijian), seven)); + List sevenClass = Lists.newArrayList(Student.of("李洛克", Lists.newArrayList(tishu), three), Student.of("日向宁次", Lists.newArrayList(tishu), three), Student.of("天天", Lists.newArrayList(tishu), three)); + + studentRepo.saveAll(threeClass); + studentRepo.saveAll(sevenClass); + + } + + /** + * 删除数据 + */ + @Transactional + public void delete() { + // 使用语句删除 + Session session = sessionFactory.openSession(); + Transaction transaction = session.beginTransaction(); + session.query("match (n)-[r]-() delete n,r", Maps.newHashMap()); + session.query("match (n)-[r]-() delete r", Maps.newHashMap()); + session.query("match (n) delete n", Maps.newHashMap()); + transaction.commit(); + + // 使用 repository 删除 + studentRepo.deleteAll(); + classRepo.deleteAll(); + lessonRepo.deleteAll(); + teacherRepo.deleteAll(); + } + + /** + * 根据学生姓名查询所选课程 + * + * @param studentName 学生姓名 + * @param depth 深度 + * @return 课程列表 + */ + public List findLessonsFromStudent(String studentName, int depth) { + List lessons = Lists.newArrayList(); + studentRepo.findByName(studentName, depth).ifPresent(student -> lessons.addAll(student.getLessons())); + return lessons; + } + + /** + * 查询全校学生数 + * + * @return 学生总数 + */ + public Long studentCount(String className) { + if (StrUtil.isBlank(className)) { + return studentRepo.count(); + } else { + return studentRepo.countByClassName(className); + } + } + + /** + * 查询同学关系,根据课程 + * + * @return 返回同学关系 + */ + public Map> findClassmatesGroupByLesson() { + List groupByLesson = studentRepo.findByClassmateGroupByLesson(); + Map> result = Maps.newHashMap(); + + groupByLesson.forEach(classmateInfoGroupByLesson -> result.put(classmateInfoGroupByLesson.getLessonName(), classmateInfoGroupByLesson.getStudents())); + + return result; + } + + /** + * 查询所有师生关系,包括班主任/学生,任课老师/学生 + * + * @return 师生关系 + */ + public Map> findTeacherStudent() { + List teacherStudentByClass = studentRepo.findTeacherStudentByClass(); + List teacherStudentByLesson = studentRepo.findTeacherStudentByLesson(); + Map> result = Maps.newHashMap(); + + teacherStudentByClass.forEach(teacherStudent -> result.put(teacherStudent.getTeacherName(), Sets.newHashSet(teacherStudent.getStudents()))); + + teacherStudentByLesson.forEach(teacherStudent -> result.put(teacherStudent.getTeacherName(), Sets.newHashSet(teacherStudent.getStudents()))); + + return result; + } +} diff --git a/spring-boot-demo-neo4j/src/main/resources/application.yml b/demo-neo4j/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-neo4j/src/main/resources/application.yml rename to demo-neo4j/src/main/resources/application.yml diff --git a/demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java b/demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java new file mode 100644 index 0000000..c7cc939 --- /dev/null +++ b/demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java @@ -0,0 +1,85 @@ +package com.xkcoding.neo4j; + +import cn.hutool.json.JSONUtil; +import com.xkcoding.neo4j.model.Lesson; +import com.xkcoding.neo4j.model.Student; +import com.xkcoding.neo4j.service.NeoService; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

    + * 测试Neo4j + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-24 15:17 + */ +@Slf4j +public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { + @Autowired + private NeoService neoService; + + /** + * 测试保存 + */ + @Test + public void testSave() { + neoService.initData(); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + neoService.delete(); + } + + /** + * 测试查询 鸣人 学了哪些课程 + */ + @Test + public void testFindLessonsByStudent() { + // 深度为1,则课程的任教老师的属性为null + // 深度为2,则会把课程的任教老师的属性赋值 + List lessons = neoService.findLessonsFromStudent("漩涡鸣人", 2); + + lessons.forEach(lesson -> log.info("【lesson】= {}", JSONUtil.toJsonStr(lesson))); + } + + /** + * 测试查询班级人数 + */ + @Test + public void testCountStudent() { + Long all = neoService.studentCount(null); + log.info("【全校人数】= {}", all); + Long seven = neoService.studentCount("第七班"); + log.info("【第七班人数】= {}", seven); + } + + /** + * 测试根据课程查询同学关系 + */ + @Test + public void testFindClassmates() { + Map> classmates = neoService.findClassmatesGroupByLesson(); + classmates.forEach((k, v) -> log.info("因为一起上了【{}】这门课,成为同学关系的有:{}", k, JSONUtil.toJsonStr(v.stream().map(Student::getName).collect(Collectors.toList())))); + } + + /** + * 查询所有师生关系,包括班主任/学生,任课老师/学生 + */ + @Test + public void testFindTeacherStudent() { + Map> teacherStudent = neoService.findTeacherStudent(); + teacherStudent.forEach((k, v) -> log.info("【{}】教的学生有 {}", k, JSONUtil.toJsonStr(v.stream().map(Student::getName).collect(Collectors.toList())))); + } +} diff --git a/spring-boot-demo-neo4j/src/test/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplicationTests.java b/demo-neo4j/src/test/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplicationTests.java similarity index 100% rename from spring-boot-demo-neo4j/src/test/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplicationTests.java rename to demo-neo4j/src/test/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplicationTests.java diff --git a/spring-boot-demo-oauth/.gitignore b/demo-oauth/.gitignore similarity index 100% rename from spring-boot-demo-oauth/.gitignore rename to demo-oauth/.gitignore diff --git a/spring-boot-demo-oauth/README.md b/demo-oauth/README.md similarity index 100% rename from spring-boot-demo-oauth/README.md rename to demo-oauth/README.md diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/README.adoc b/demo-oauth/oauth-authorization-server/README.adoc similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/README.adoc rename to demo-oauth/oauth-authorization-server/README.adoc diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Code.png b/demo-oauth/oauth-authorization-server/image/Code.png similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Code.png rename to demo-oauth/oauth-authorization-server/image/Code.png diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Confirm.png b/demo-oauth/oauth-authorization-server/image/Confirm.png similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Confirm.png rename to demo-oauth/oauth-authorization-server/image/Confirm.png diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Login.png b/demo-oauth/oauth-authorization-server/image/Login.png similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Login.png rename to demo-oauth/oauth-authorization-server/image/Login.png diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Logout.png b/demo-oauth/oauth-authorization-server/image/Logout.png similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/image/Logout.png rename to demo-oauth/oauth-authorization-server/image/Logout.png diff --git a/demo-oauth/oauth-authorization-server/pom.xml b/demo-oauth/oauth-authorization-server/pom.xml new file mode 100644 index 0000000..e1b5bee --- /dev/null +++ b/demo-oauth/oauth-authorization-server/pom.xml @@ -0,0 +1,44 @@ + + + + demo-oauth + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + oauth-authorization-server + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security.oauth.boot + spring-security-oauth2-autoconfigure + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java new file mode 100644 index 0000000..af2dd99 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.oauth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-02-17 23:52 + */ +@SpringBootApplication +public class SpringBootDemoOauthApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOauthApplication.class, args); + } + +} + diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java new file mode 100644 index 0000000..8fe249d --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java @@ -0,0 +1,29 @@ +package com.xkcoding.oauth.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; + +/** + * 登录失败处理器,失败后携带失败信息重定向到登录地址重新登录. + * + * @author EchoCow + * @date 2020-01-07 13:01 + */ +@Slf4j +@Component +public class ClientLoginFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + log.debug("Login failed!"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.sendRedirect("/oauth/login?error=" + URLEncoder.encode(exception.getLocalizedMessage(), "UTF-8")); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java new file mode 100644 index 0000000..61f9f35 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java @@ -0,0 +1,30 @@ +package com.xkcoding.oauth.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 客户团退出登录成功处理器. + * + * @author EchoCow + * @date 2020-01-06 22:11 + */ +@Slf4j +@Component +public class ClientLogoutSuccessHandler implements LogoutSuccessHandler { + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + response.setStatus(HttpStatus.FOUND.value()); + // 跳转到客户端的回调地址 + response.sendRedirect(request.getParameter("redirectUrl")); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java new file mode 100644 index 0000000..228ec84 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java @@ -0,0 +1,51 @@ +package com.xkcoding.oauth.config; + +import com.xkcoding.oauth.service.SysClientDetailsService; +import com.xkcoding.oauth.service.SysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 13:32 + */ +@Configuration +@RequiredArgsConstructor +@EnableAuthorizationServer +public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + private final SysClientDetailsService sysClientDetailsService; + private final SysUserService sysUserService; + private final TokenStore tokenStore; + private final AuthenticationManager authenticationManager; + private final JwtAccessTokenConverter jwtAccessTokenConverter; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + endpoints.authenticationManager(authenticationManager).userDetailsService(sysUserService).tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 从数据库读取我们自定义的客户端信息 + clients.withClientDetails(sysClientDetailsService); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) { + security + // 获取 token key 需要进行 basic 认证客户端信息 + .tokenKeyAccess("isAuthenticated()") + // 获取 token 信息同样需要 basic 认证客户端信息 + .checkTokenAccess("isAuthenticated()"); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java new file mode 100644 index 0000000..c002434 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java @@ -0,0 +1,74 @@ +package com.xkcoding.oauth.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; + +import java.security.KeyPair; + +/** + * token 相关配置. + * + * @author EchoCow + * @date 2020-01-06 13:33 + */ +@Configuration +@RequiredArgsConstructor +public class Oauth2AuthorizationTokenConfig { + + /** + * 声明 内存 TokenStore 实现,用来存储 token 相关. + * 默认实现有 mysql、redis + * + * @return InMemoryTokenStore + */ + @Bean + @Primary + public TokenStore tokenStore() { + return new InMemoryTokenStore(); + } + + /** + * jwt 令牌 配置,非对称加密 + * + * @return 转换器 + */ + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + final JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); + accessTokenConverter.setKeyPair(keyPair()); + return accessTokenConverter; + } + + /** + * 密钥 keyPair. + * 可用于生成 jwt / jwk. + * + * @return keyPair + */ + @Bean + public KeyPair keyPair() { + KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "123456".toCharArray()); + return keyStoreKeyFactory.getKeyPair("oauth2"); + } + + /** + * 加密方式,使用 BCrypt. + * 参数越大加密次数越多,时间越久. + * 默认为 10. + * + * @return PasswordEncoder + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java new file mode 100644 index 0000000..c9d2531 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java @@ -0,0 +1,41 @@ +package com.xkcoding.oauth.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * 安全配置. + * + * @author EchoCow + * @date 2020-01-06 13:27 + */ +@EnableWebSecurity +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final ClientLogoutSuccessHandler clientLogoutSuccessHandler; + private final ClientLoginFailureHandler clientLoginFailureHandler; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.formLogin().loginPage("/oauth/login").failureHandler(clientLoginFailureHandler).loginProcessingUrl("/authorization/form").and().logout().logoutUrl("/oauth/logout").logoutSuccessHandler(clientLogoutSuccessHandler).and().authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated(); + } + + /** + * 授权管理. + * + * @return 认证管理对象 + * @throws Exception 认证异常信息 + */ + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java new file mode 100644 index 0000000..f1cc4ed --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java @@ -0,0 +1,21 @@ +/** + * spring security oauth2 的相关配置。 + * 使用 spring boot oauth2 自动配置。 + * {@link com.xkcoding.oauth.config.Oauth2AuthorizationServerConfig} + * 授权服务器相关的配置,主要设置授权服务器如何读取客户端、用户信息和一些端点配置 + * 可以在这里配置更多的东西,例如端点映射,token 增强等 + *

    + * {@link com.xkcoding.oauth.config.Oauth2AuthorizationTokenConfig} + * 授权服务器 token 相关的配置,主要设置 jwt、加密方式等信息 + *

    + * {@link com.xkcoding.oauth.config.ClientLogoutSuccessHandler} + * 资源服务器退出以后的处理。在授权码模式中,所有的客户端都需要跳转到授权服务器进行登录 + * 当登录成功以后跳转到回调地址,如果用户需要登出,也要跳转到授权服务器这里进行登出 + * 但是 spring security oauth2 似乎并没有这个逻辑。 + * 所以自己给登出端点加了一个 redirect_url 参数,表示登出成功以后要跳转的地址 + * 这个处理器就是来完成登出成功以后的跳转操作的。 + * + * @author EchoCow + * @date 2020-01-07 9:16 + */ +package com.xkcoding.oauth.config; diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java new file mode 100644 index 0000000..a7e26e2 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java @@ -0,0 +1,43 @@ +package com.xkcoding.oauth.controller; + +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.servlet.ModelAndView; + +import java.util.Map; + +/** + * 自定义确认授权页面. + * 需要注意的是: 不能在代码中 setComplete,因为整个授权流程并没有结束 + * 我们只是在中途修改了它确认的一些信息而已。 + * + * @author EchoCow + * @date 2020-01-06 16:42 + */ +@Controller +@SessionAttributes("authorizationRequest") +public class AuthorizationController { + + /** + * 自定义确认授权页面 + * 当然你也可以使用 {@link AuthorizationEndpoint#setUserApprovalPage(String)} 方法 + * 进行设置,但是 model 就没有那么灵活了 + * + * @param model model + * @return ModelAndView + */ + @GetMapping("/oauth/confirm_access") + public ModelAndView getAccessConfirmation(Map model) { + AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); + ModelAndView view = new ModelAndView(); + view.setViewName("authorization"); + view.addObject("clientId", authorizationRequest.getClientId()); + // 传递 scope 过去,Set 集合 + view.addObject("scopes", authorizationRequest.getScope()); + return view; + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java new file mode 100644 index 0000000..a3938fa --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java @@ -0,0 +1,54 @@ +package com.xkcoding.oauth.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.servlet.ModelAndView; + +import java.security.Principal; +import java.util.Objects; + +/** + * 页面控制器. + * + * @author EchoCow + * @date 2020-01-06 16:30 + */ +@Controller +@RequestMapping("/oauth") +@RequiredArgsConstructor +public class Oauth2Controller { + + /** + * 授权码模式跳转到登录页面 + * + * @return view + */ + @GetMapping("/login") + public String loginView() { + return "login"; + } + + /** + * 退出登录 + * + * @param redirectUrl 退出完成后的回调地址 + * @param principal 用户信息 + * @return 结果 + */ + @GetMapping("/logout") + public ModelAndView logoutView(@RequestParam("redirect_url") String redirectUrl, Principal principal) { + if (Objects.isNull(principal)) { + throw new ResourceAccessException("请求错误,用户尚未登录"); + } + ModelAndView view = new ModelAndView(); + view.setViewName("logout"); + view.addObject("user", principal.getName()); + view.addObject("redirectUrl", redirectUrl); + return view; + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java new file mode 100644 index 0000000..a4c53ec --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java @@ -0,0 +1,14 @@ +/** + * 控制器。除了业务逻辑的以外,提供两个控制器来帮助完成自定义: + * {@link com.xkcoding.oauth.controller.AuthorizationController} + * 自定义的授权控制器,重新设置到我们的界面中去,不使用他的默认实现 + *

    + * {@link com.xkcoding.oauth.controller.Oauth2Controller} + * 页面跳转的控制器,这里拿出来是因为真的可以做很多事。比如登录的时候携带点什么 + * 或者退出的时候携带什么标识,都可以。 + * + * @author EchoCow + * @date 2020-01-07 11:25 + * @see org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint + */ +package com.xkcoding.oauth.controller; diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java new file mode 100644 index 0000000..7562d28 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java @@ -0,0 +1,191 @@ +package com.xkcoding.oauth.entity; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; + +import javax.persistence.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 客户端信息. + * 这里实现了 ClientDetails 接口 + * 个人建议不应该在实体类里面写任何逻辑代码 + * 而为了避免实体类耦合严重不应该去实现这个接口的 + * 但是这里为了演示和 {@link SysUser} 不同的方式,所以就选择实现这个接口了 + * 另一种方式是写一个方法将它转化为默认实现 {@link BaseClientDetails} 比较好一点并且简单很多 + * + * @author EchoCow + * @date 2020-01-06 12:54 + */ +@Data +@Table +@Entity +public class SysClientDetails implements ClientDetails { + + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * client id + */ + private String clientId; + + /** + * client 密钥 + */ + private String clientSecret; + + /** + * 资源服务器名称 + */ + private String resourceIds; + + /** + * 授权域 + */ + private String scopes; + + /** + * 授权类型 + */ + private String grantTypes; + + /** + * 重定向地址,授权码时必填 + */ + private String redirectUrl; + + /** + * 授权信息 + */ + private String authorizations; + + /** + * 授权令牌有效时间 + */ + private Integer accessTokenValiditySeconds; + + /** + * 刷新令牌有效时间 + */ + private Integer refreshTokenValiditySeconds; + + /** + * 自动授权请求域 + */ + private String autoApproveScopes; + + /** + * 是否安全 + * + * @return 结果 + */ + @Override + public boolean isSecretRequired() { + return this.clientSecret != null; + } + + /** + * 是否有 scopes + * + * @return 结果 + */ + @Override + public boolean isScoped() { + return this.scopes != null && !this.scopes.isEmpty(); + } + + /** + * scopes + * + * @return scopes + */ + @Override + public Set getScope() { + return stringToSet(scopes); + } + + /** + * 授权类型 + * + * @return 结果 + */ + @Override + public Set getAuthorizedGrantTypes() { + return stringToSet(grantTypes); + } + + @Override + public Set getResourceIds() { + return stringToSet(resourceIds); + } + + + /** + * 获取回调地址 + * + * @return redirectUrl + */ + @Override + public Set getRegisteredRedirectUri() { + return stringToSet(redirectUrl); + } + + /** + * 这里需要提一下 + * 个人觉得这里应该是客户端所有的权限 + * 但是已经有 scope 的存在可以很好的对客户端的权限进行认证了 + * 那么在 oauth2 的四个角色中,这里就有可能是资源服务器的权限 + * 但是一般资源服务器都有自己的权限管理机制,比如拿到用户信息后做 RBAC + * 所以在 spring security 的默认实现中直接给的是空的一个集合 + * 这里我们也给他一个空的把 + * + * @return GrantedAuthority + */ + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + /** + * 判断是否自动授权 + * + * @param scope scope + * @return 结果 + */ + @Override + public boolean isAutoApprove(String scope) { + if (autoApproveScopes == null || autoApproveScopes.isEmpty()) { + return false; + } + Set authorizationSet = stringToSet(authorizations); + for (String auto : authorizationSet) { + if ("true".equalsIgnoreCase(auto) || scope.matches(auto)) { + return true; + } + } + return false; + } + + /** + * additional information 是 spring security 的保留字段 + * 暂时用不到,直接给个空的即可 + * + * @return map + */ + @Override + public Map getAdditionalInformation() { + return Collections.emptyMap(); + } + + private Set stringToSet(String s) { + return Arrays.stream(s.split(",")).collect(Collectors.toSet()); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java new file mode 100644 index 0000000..a5362e6 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java @@ -0,0 +1,48 @@ +package com.xkcoding.oauth.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.persistence.*; +import java.util.Set; + +/** + * 这里完全可以只用一个字段代替的 + * 但是想了想还是模拟实际的情况来把 + * 角色信息. + * + * @author EchoCow + * @date 2020-01-06 12:44 + */ +@Data +@Table +@Entity +@EqualsAndHashCode(exclude = {"users"}) +@ToString(exclude = "users") +public class SysRole { + + /** + * 主键. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 角色名称,按照 spring security 规范 + * 需要以 ROLE_ 开头. + */ + private String name; + + /** + * 角色描述. + */ + private String description; + + /** + * 当前角色所有用户. + */ + @ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER) + private Set users; +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java new file mode 100644 index 0000000..4a04933 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java @@ -0,0 +1,52 @@ +package com.xkcoding.oauth.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import java.util.Set; + +/** + * 用户实体. + * 避免实体类耦合,所以不去实现 {@link UserDetails} 接口 + * 因为有且只有登录加载用户的时候才会需要这个接口 + * 我们就手动构建一个 {@link User} 的默认实现就可以了 + * 实现接口的方式可以参考 {@link SysClientDetails} + * + * @author EchoCow + * @date 2020-01-06 12:41 + */ +@Data +@Table +@Entity +@EqualsAndHashCode(exclude = "roles") +@ToString(exclude = "roles") +public class SysUser { + + /** + * 主键. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 用户名. + */ + private String username; + + /** + * 密码. + */ + private String password; + + /** + * 当前用户所有角色. + */ + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "sys_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles; +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java new file mode 100644 index 0000000..83dcfd1 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java @@ -0,0 +1,33 @@ +package com.xkcoding.oauth.repostiory; + +import com.xkcoding.oauth.entity.SysClientDetails; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; + +import java.util.Optional; + +/** + * 客户端信息. + * + * @author EchoCow + * @date 2020-01-06 13:09 + */ +public interface SysClientDetailsRepository extends JpaRepository { + + /** + * 通过 clientId 查找客户端信息. + * + * @param clientId clientId + * @return 结果 + */ + Optional findFirstByClientId(String clientId); + + /** + * 根据客户端 id 删除客户端 + * + * @param clientId 客户端id + */ + @Modifying + void deleteByClientId(String clientId); + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java new file mode 100644 index 0000000..0145759 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java @@ -0,0 +1,24 @@ +package com.xkcoding.oauth.repostiory; + +import com.xkcoding.oauth.entity.SysUser; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +/** + * 用户信息仓库. + * + * @author EchoCow + * @date 2020-01-06 13:08 + */ +public interface SysUserRepository extends JpaRepository { + + /** + * 通过用户名查找用户. + * + * @param username 用户名 + * @return 结果 + */ + Optional findFirstByUsername(String username); + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java new file mode 100644 index 0000000..5ccbdbd --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java @@ -0,0 +1,67 @@ +package com.xkcoding.oauth.service; + +import com.xkcoding.oauth.entity.SysClientDetails; +import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationService; +import org.springframework.security.oauth2.provider.NoSuchClientException; + +import java.util.List; + +/** + * 声明自己的实现. + * 参见 {@link ClientRegistrationService} + * + * @author EchoCow + * @date 2020-01-06 13:39 + */ +public interface SysClientDetailsService extends ClientDetailsService { + + /** + * 通过客户端 id 查询 + * + * @param clientId 客户端 id + * @return 结果 + */ + SysClientDetails findByClientId(String clientId); + + /** + * 添加客户端信息. + * + * @param clientDetails 客户端信息 + * @throws ClientAlreadyExistsException 客户端已存在 + */ + void addClientDetails(SysClientDetails clientDetails) throws ClientAlreadyExistsException; + + /** + * 更新客户端信息,不包括 clientSecret. + * + * @param clientDetails 客户端信息 + * @throws NoSuchClientException 找不到客户端异常 + */ + void updateClientDetails(SysClientDetails clientDetails) throws NoSuchClientException; + + /** + * 更新客户端密钥. + * + * @param clientId 客户端 id + * @param clientSecret 客户端密钥 + * @throws NoSuchClientException 找不到客户端异常 + */ + void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException; + + /** + * 删除客户端信息. + * + * @param clientId 客户端 id + * @throws NoSuchClientException 找不到客户端异常 + */ + void removeClientDetails(String clientId) throws NoSuchClientException; + + /** + * 查询所有 + * + * @return 结果 + */ + List findAll(); +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java new file mode 100644 index 0000000..95b8fa9 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java @@ -0,0 +1,59 @@ +package com.xkcoding.oauth.service; + +import com.xkcoding.oauth.entity.SysUser; +import org.springframework.security.core.userdetails.UserDetailsService; + +import java.util.List; + + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 15:44 + */ +public interface SysUserService extends UserDetailsService { + /** + * 查询所有用户 + * + * @return 用户 + */ + List findAll(); + + /** + * 通过 id 查询用户 + * + * @param id id + * @return 用户 + */ + SysUser findById(Long id); + + /** + * 创建用户 + * + * @param sysUser 用户 + */ + void createUser(SysUser sysUser); + + /** + * 更新用户 + * + * @param sysUser 用户 + */ + void updateUser(SysUser sysUser); + + /** + * 更新用户 密码 + * + * @param id 用户 id + * @param password 用户密码 + */ + void updatePassword(Long id, String password); + + /** + * 删除用户. + * + * @param id id + */ + void deleteUser(Long id); +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java new file mode 100644 index 0000000..7741ad6 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java @@ -0,0 +1,72 @@ +package com.xkcoding.oauth.service.impl; + +import com.xkcoding.oauth.entity.SysClientDetails; +import com.xkcoding.oauth.repostiory.SysClientDetailsRepository; +import com.xkcoding.oauth.service.SysClientDetailsService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 客户端 相关操作. + * + * @author EchoCow + * @date 2020-01-06 13:37 + */ +@Service +@RequiredArgsConstructor +public class SysClientDetailsServiceImpl implements SysClientDetailsService { + + private final SysClientDetailsRepository sysClientDetailsRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public ClientDetails loadClientByClientId(String id) throws ClientRegistrationException { + return sysClientDetailsRepository.findFirstByClientId(id).orElseThrow(() -> new ClientRegistrationException("Loading client exception.")); + } + + @Override + public SysClientDetails findByClientId(String clientId) { + return sysClientDetailsRepository.findFirstByClientId(clientId).orElseThrow(() -> new ClientRegistrationException("Loading client exception.")); + } + + @Override + public void addClientDetails(SysClientDetails clientDetails) throws ClientAlreadyExistsException { + clientDetails.setId(null); + if (sysClientDetailsRepository.findFirstByClientId(clientDetails.getClientId()).isPresent()) { + throw new ClientAlreadyExistsException(String.format("Client id %s already exist.", clientDetails.getClientId())); + } + sysClientDetailsRepository.save(clientDetails); + } + + @Override + public void updateClientDetails(SysClientDetails clientDetails) throws NoSuchClientException { + SysClientDetails exist = sysClientDetailsRepository.findFirstByClientId(clientDetails.getClientId()).orElseThrow(() -> new NoSuchClientException("No such client!")); + clientDetails.setClientSecret(exist.getClientSecret()); + sysClientDetailsRepository.save(clientDetails); + } + + @Override + public void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException { + SysClientDetails exist = sysClientDetailsRepository.findFirstByClientId(clientId).orElseThrow(() -> new NoSuchClientException("No such client!")); + exist.setClientSecret(passwordEncoder.encode(clientSecret)); + sysClientDetailsRepository.save(exist); + } + + @Override + public void removeClientDetails(String clientId) throws NoSuchClientException { + sysClientDetailsRepository.deleteByClientId(clientId); + } + + @Override + public List findAll() { + return sysClientDetailsRepository.findAll(); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..68068f6 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java @@ -0,0 +1,72 @@ +package com.xkcoding.oauth.service.impl; + +import com.xkcoding.oauth.entity.SysUser; +import com.xkcoding.oauth.repostiory.SysUserRepository; +import com.xkcoding.oauth.service.SysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户相关操作. + * + * @author EchoCow + * @date 2020-01-06 15:06 + */ +@Service +@RequiredArgsConstructor +public class SysUserServiceImpl implements SysUserService { + + private final SysUserRepository sysUserRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUser sysUser = sysUserRepository.findFirstByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found!")); + List roles = sysUser.getRoles().stream().map(sysRole -> new SimpleGrantedAuthority(sysRole.getName())).collect(Collectors.toList()); + // 在这里手动构建 UserDetails 的默认实现 + return new User(sysUser.getUsername(), sysUser.getPassword(), roles); + } + + @Override + public List findAll() { + return sysUserRepository.findAll(); + } + + @Override + public SysUser findById(Long id) { + return sysUserRepository.findById(id).orElseThrow(() -> new RuntimeException("找不到用户")); + } + + @Override + public void createUser(SysUser sysUser) { + sysUser.setId(null); + sysUserRepository.save(sysUser); + } + + @Override + public void updateUser(SysUser sysUser) { + sysUser.setPassword(null); + sysUserRepository.save(sysUser); + } + + @Override + public void updatePassword(Long id, String password) { + SysUser exist = findById(id); + exist.setPassword(passwordEncoder.encode(password)); + sysUserRepository.save(exist); + } + + @Override + public void deleteUser(Long id) { + sysUserRepository.deleteById(id); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java new file mode 100644 index 0000000..b10f52f --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java @@ -0,0 +1,7 @@ +/** + * service 层,继承并实现 spring 接口. + * + * @author EchoCow + * @date 2020-01-07 9:16 + */ +package com.xkcoding.oauth.service; diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/application.yml b/demo-oauth/oauth-authorization-server/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/application.yml rename to demo-oauth/oauth-authorization-server/src/main/resources/application.yml diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/oauth2.jks b/demo-oauth/oauth-authorization-server/src/main/resources/oauth2.jks similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/oauth2.jks rename to demo-oauth/oauth-authorization-server/src/main/resources/oauth2.jks diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/public.txt b/demo-oauth/oauth-authorization-server/src/main/resources/public.txt similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/public.txt rename to demo-oauth/oauth-authorization-server/src/main/resources/public.txt diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/authorization.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/authorization.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/authorization.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/authorization.html diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/common/common.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/common/common.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/common/common.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/common/common.html diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/error.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/error.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/error.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/error.html diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/login.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/login.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/login.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/login.html diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/logout.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/logout.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/logout.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/logout.html diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/registerTemplate.html b/demo-oauth/oauth-authorization-server/src/main/resources/templates/registerTemplate.html similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/resources/templates/registerTemplate.html rename to demo-oauth/oauth-authorization-server/src/main/resources/templates/registerTemplate.html diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java new file mode 100644 index 0000000..84cd8bb --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java @@ -0,0 +1,22 @@ +package com.xkcoding.oauth; + +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 15:51 + */ +public class PasswordEncodeTest { + + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Test + public void getPasswordWhenPassed() { + System.out.println(passwordEncoder.encode("oauth2")); + System.out.println(passwordEncoder.encode("123456")); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java new file mode 100644 index 0000000..0679dc5 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java @@ -0,0 +1,121 @@ +package com.xkcoding.oauth.oauth; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.xkcoding.oauth.oauth.AuthorizationServerInfo.getUrl; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 授权码模式测试. + * + * @author EchoCow + * @date 2020-01-06 20:43 + */ +public class AuthorizationCodeGrantTests { + + private AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + private AuthorizationServerInfo authorizationServerInfo = new AuthorizationServerInfo(); + + @BeforeEach + void setUp() { + resource.setAccessTokenUri(getUrl("/oauth/token")); + resource.setClientId("oauth2"); + resource.setId("oauth2"); + resource.setScope(Arrays.asList("READ", "WRITE")); + resource.setAccessTokenUri(getUrl("/oauth/token")); + resource.setUserAuthorizationUri(getUrl("/oauth/authorize")); + } + + @Test + void testCannotConnectWithoutToken() { + OAuth2RestTemplate template = new OAuth2RestTemplate(resource); + assertThrows(UserRedirectRequiredException.class, () -> template.getForObject(getUrl("/oauth/me"), String.class)); + } + + @Test + void testAttemptedTokenAcquisitionWithNoRedirect() { + AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider(); + assertThrows(UserRedirectRequiredException.class, () -> provider.obtainAccessToken(resource, new DefaultAccessTokenRequest())); + } + + /** + * 这里不使用他提供的是因为很多地方不符合我们的需要 + * 比如 csrf,比如许多有些是自己自定义的端点这些 + * 所以只有我们一步一步的来进行测试拿到授权码 + */ + @Test + void testCodeAcquisitionWithCorrectContext() { + // 1. 请求登录页面获取 _csrf 的 value 以及 cookie + ResponseEntity page = authorizationServerInfo.getForString("/oauth/login"); + assertNotNull(page.getBody()); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); + assertTrue(matcher.find()); + + // 2. 添加表单数据 + MultiValueMap form = new LinkedMultiValueMap<>(); + form.add("username", "admin"); + form.add("password", "123456"); + form.add("_csrf", matcher.group(1)); + + // 3. 登录授权并获取登录成功的 cookie + ResponseEntity response = authorizationServerInfo.postForStatus("/authorization/form", headers, form); + assertNotNull(response); + cookie = response.getHeaders().getFirst("Set-Cookie"); + headers = new HttpHeaders(); + headers.set("Cookie", cookie); + headers.setAccept(Collections.singletonList(MediaType.ALL)); + + // 4. 请求到 确认授权页面 ,获取确认授权页面的 _csrf 的 value + ResponseEntity confirm = authorizationServerInfo.getForString("/oauth/authorize?response_type=code&client_id=oauth2&redirect_uri=http://example.com&scope=READ", headers); + + headers = confirm.getHeaders(); + // 确认过一次后,后面都会自动确认了,这里判断下是不是重定向请求 + // 如果不是,就表示是第一次,需要确认授权 + if (!confirm.getStatusCode().is3xxRedirection()) { + assertNotNull(confirm.getBody()); + Matcher matcherConfirm = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(confirm.getBody()); + assertTrue(matcherConfirm.find()); + headers = new HttpHeaders(); + headers.set("Cookie", cookie); + headers.setAccept(Collections.singletonList(MediaType.ALL)); + + // 5. 构建 同意授权 的表单 + form = new LinkedMultiValueMap<>(); + form.add("user_oauth_approval", "true"); + form.add("scope.READ", "true"); + form.add("_csrf", matcherConfirm.group(1)); + + // 6. 请求授权,获取 授权码 + headers = authorizationServerInfo.postForHeaders("/oauth/authorize", form, headers); + } + + URI location = headers.getLocation(); + assertNotNull(location); + String query = location.getQuery(); + assertNotNull(query); + String[] result = query.split("="); + assertEquals(2, result.length); + System.out.println(result[1]); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java new file mode 100644 index 0000000..ff1b99e --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java @@ -0,0 +1,92 @@ +package com.xkcoding.oauth.oauth; + +import org.springframework.http.*; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RequestCallback; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.HttpURLConnection; + +/** + * 授权服务器工具类. + * + * @author EchoCow + * @date 2020-01-06 20:44 + */ +@SuppressWarnings("all") +public class AuthorizationServerInfo { + public static final String HOST = "http://127.0.0.1:8080"; + + private RestTemplate client; + + public AuthorizationServerInfo() { + client = new RestTemplate(); + client.setRequestFactory(new SimpleClientHttpRequestFactory() { + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + super.prepareConnection(connection, httpMethod); + connection.setInstanceFollowRedirects(false); + } + }); + client.setErrorHandler(new ResponseErrorHandler() { + public boolean hasError(ClientHttpResponse response) { + return false; + } + + public void handleError(ClientHttpResponse response) { + } + }); + } + + public ResponseEntity getForString(String path, final HttpHeaders headers) { + return client.exchange(getUrl(path), HttpMethod.GET, new HttpEntity<>(null, headers), String.class); + } + + public ResponseEntity getForString(String path) { + return getForString(path, new HttpHeaders()); + } + + public ResponseEntity postForStatus(String path, HttpHeaders headers, MultiValueMap formData) { + HttpHeaders actualHeaders = new HttpHeaders(); + actualHeaders.putAll(headers); + actualHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + return client.exchange(getUrl(path), HttpMethod.POST, new HttpEntity<>(formData, actualHeaders), (Class) null); + } + + + public static String getUrl(String path) { + return HOST + path; + } + + public HttpHeaders postForHeaders(String path, MultiValueMap formData, final HttpHeaders headers) { + RequestCallback requestCallback = new NullRequestCallback(); + if (headers != null) { + requestCallback = request -> request.getHeaders().putAll(headers); + } + StringBuilder builder = new StringBuilder(getUrl(path)); + if (!path.contains("?")) { + builder.append("?"); + } else { + builder.append("&"); + } + for (String key : formData.keySet()) { + for (String value : formData.get(key)) { + builder.append(key).append("=").append(value); + builder.append("&"); + } + } + builder.deleteCharAt(builder.length() - 1); + + return client.execute(builder.toString(), HttpMethod.POST, requestCallback, HttpMessage::getHeaders); + } + + private static final class NullRequestCallback implements RequestCallback { + public void doWithRequest(ClientHttpRequest request) { + } + } +} diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java new file mode 100644 index 0000000..2955a7b --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java @@ -0,0 +1,39 @@ +package com.xkcoding.oauth.oauth; + +import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +import java.util.Arrays; + +import static com.xkcoding.oauth.oauth.AuthorizationServerInfo.getUrl; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 21:14 + */ +public class ResourceOwnerPasswordGrantTests { + + @Test + void testConnectDirectlyToResourceServer() { + assertNotNull(accessToken()); + } + + public static String accessToken() { + ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); + resource.setAccessTokenUri(getUrl("/oauth/token")); + resource.setClientId("oauth2"); + resource.setClientSecret("oauth2"); + resource.setId("oauth2"); + resource.setScope(Arrays.asList("READ", "WRITE")); + resource.setUsername("admin"); + resource.setPassword("123456"); + OAuth2RestTemplate template = new OAuth2RestTemplate(resource); + OAuth2AccessToken accessToken = template.getAccessToken(); + return accessToken.getValue(); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java new file mode 100644 index 0000000..515ebe0 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java @@ -0,0 +1,26 @@ +package com.xkcoding.oauth.repostiory; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 13:10 + */ +@DataJpaTest +public class SysClientDetailsTest { + @Autowired + private SysClientDetailsRepository sysClientDetailsRepository; + + @Test + public void autowiredSuccessWhenPassed() { + assertNotNull(sysClientDetailsRepository); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java new file mode 100644 index 0000000..50903d1 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java @@ -0,0 +1,40 @@ +package com.xkcoding.oauth.repostiory; + +import com.xkcoding.oauth.entity.SysUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 13:25 + */ +@DataJpaTest +public class SysUserRepositoryTest { + + @Autowired + private SysUserRepository sysUserRepository; + + @Test + public void autowiredSuccessWhenPassed() { + assertNotNull(sysUserRepository); + } + + @Test + @DisplayName("测试关联查询") + public void queryUserAndRoleWhenPassed() { + Optional admin = sysUserRepository.findFirstByUsername("admin"); + assertTrue(admin.isPresent()); + SysUser sysUser = admin.orElseGet(SysUser::new); + assertNotNull(sysUser.getRoles()); + assertEquals(1, sysUser.getRoles().size()); + } +} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/application.yml b/demo-oauth/oauth-authorization-server/src/test/resources/application.yml similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/application.yml rename to demo-oauth/oauth-authorization-server/src/test/resources/application.yml diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/import.sql b/demo-oauth/oauth-authorization-server/src/test/resources/import.sql similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/import.sql rename to demo-oauth/oauth-authorization-server/src/test/resources/import.sql diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/schema.sql b/demo-oauth/oauth-authorization-server/src/test/resources/schema.sql similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/resources/schema.sql rename to demo-oauth/oauth-authorization-server/src/test/resources/schema.sql diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/README.adoc b/demo-oauth/oauth-resource-server/README.adoc similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/README.adoc rename to demo-oauth/oauth-resource-server/README.adoc diff --git a/demo-oauth/oauth-resource-server/pom.xml b/demo-oauth/oauth-resource-server/pom.xml new file mode 100644 index 0000000..f5eef9f --- /dev/null +++ b/demo-oauth/oauth-resource-server/pom.xml @@ -0,0 +1,31 @@ + + + + demo-oauth + com.xkcoding + 1.0.0-SNAPSHOT + + 4.0.0 + + oauth-resource-server + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security.oauth.boot + spring-security-oauth2-autoconfigure + ${spring.boot.version} + + + diff --git a/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java new file mode 100644 index 0000000..b8b24b4 --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.oauth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; + +/** + * 启动器. + * + * @author EchoCow + * @version V1.0 + * @date 2020-01-09 11:38 + */ +@EnableResourceServer +@SpringBootApplication +public class SpringBootDemoResourceApplication { + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoResourceApplication.class, args); + } + +} diff --git a/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java new file mode 100644 index 0000000..3985304 --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java @@ -0,0 +1,41 @@ +package com.xkcoding.oauth.config; + +import lombok.AllArgsConstructor; +import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.TokenStore; + +/** + * 资源服务器配置. + * 我们自己实现了它的配置,所以它的自动装配不会生效 + * + * @author EchoCow + * @date 2020-01-09 14:20 + */ +@Configuration +@AllArgsConstructor +@EnableResourceServer +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class OauthResourceServerConfig extends ResourceServerConfigurerAdapter { + + private final ResourceServerProperties resourceServerProperties; + private final TokenStore tokenStore; + + @Override + public void configure(ResourceServerSecurityConfigurer resources) { + resources.tokenStore(tokenStore).resourceId(resourceServerProperties.getResourceId()); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + super.configure(http); + // 前后端分离下,可以关闭 csrf + http.csrf().disable(); + } + +} diff --git a/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java new file mode 100644 index 0000000..2ddebdc --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java @@ -0,0 +1,98 @@ +package com.xkcoding.oauth.config; + +import cn.hutool.json.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.Base64; + +/** + * token 相关配置,jwt 相关. + * + * @author EchoCow + * @date 2020-01-09 14:39 + */ +@Slf4j +@Configuration +@AllArgsConstructor +public class OauthResourceTokenConfig { + + private final ResourceServerProperties resourceServerProperties; + + /** + * 这里并不是对令牌的存储,他将访问令牌与身份验证进行转换 + * 在需要 {@link TokenStore} 的任何地方可以使用此方法 + * + * @return TokenStore + */ + @Bean + public TokenStore tokenStore() { + return new JwtTokenStore(jwtAccessTokenConverter()); + } + + /** + * jwt 令牌转换 + * + * @return jwt + */ + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + converter.setVerifierKey(getPubKey()); + return converter; + } + + /** + * 非对称密钥加密,获取 public key。 + * 自动选择加载方式。 + * + * @return public key + */ + private String getPubKey() { + // 如果本地没有密钥,就从授权服务器中获取 + return StringUtils.isEmpty(resourceServerProperties.getJwt().getKeyValue()) ? getKeyFromAuthorizationServer() : resourceServerProperties.getJwt().getKeyValue(); + } + + /** + * 本地没有公钥的时候,从服务器上获取 + * 需要进行 Basic 认证 + * + * @return public key + */ + private String getKeyFromAuthorizationServer() { + ObjectMapper objectMapper = new ObjectMapper(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(HttpHeaders.AUTHORIZATION, encodeClient()); + HttpEntity requestEntity = new HttpEntity<>(null, httpHeaders); + String pubKey = new RestTemplate().getForObject(resourceServerProperties.getJwt().getKeyUri(), String.class, requestEntity); + try { + JSONObject body = objectMapper.readValue(pubKey, JSONObject.class); + log.info("Get Key From Authorization Server."); + return body.getStr("value"); + } catch (IOException e) { + log.error("Get public key error: {}", e.getMessage()); + } + return null; + } + + /** + * 客户端信息 + * + * @return basic + */ + private String encodeClient() { + return "Basic " + Base64.getEncoder().encodeToString((resourceServerProperties.getClientId() + ":" + resourceServerProperties.getClientSecret()).getBytes()); + } +} diff --git a/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java new file mode 100644 index 0000000..b3f1572 --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java @@ -0,0 +1,60 @@ +package com.xkcoding.oauth.controller; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 测试接口. + * + * @author EchoCow + * @date 2020-01-09 14:37 + */ +@RestController +public class TestController { + + /** + * 拥有 ROLE_ADMIN 的用户才能访问的资源 + * + * @return ADMIN + */ + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/admin") + public String admin() { + return "ADMIN"; + } + + /** + * 拥有 ROLE_TEST 的用户才能访问的资源 + * + * @return TEST + */ + @PreAuthorize("hasRole('TEST')") + @GetMapping("/test") + public String test() { + return "TEST"; + } + + /** + * scope 有 READ 的用户资源才能访问 + * + * @return READ + */ + @PreAuthorize("#oauth2.hasScope('READ')") + @GetMapping("/read") + public String read() { + return "READ"; + } + + /** + * scope 有 WRITE 的用户资源才能访问 + * + * @return WRITE + */ + @PreAuthorize("#oauth2.hasScope('WRITE')") + @GetMapping("/write") + public String write() { + return "WRITE"; + } + +} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/resources/application.yml b/demo-oauth/oauth-resource-server/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/resources/application.yml rename to demo-oauth/oauth-resource-server/src/main/resources/application.yml diff --git a/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java b/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java new file mode 100644 index 0000000..c830f33 --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java @@ -0,0 +1,37 @@ +package com.xkcoding.oauth; + +import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * . + * + * @author EchoCow + * @date 2020-01-09 15:44 + */ +public class AuthorizationTest { + public static final String AUTHORIZATION_SERVER = "http://127.0.0.1:8080"; + + protected OAuth2RestTemplate oauth2RestTemplate(String username, String password, List scope) { + ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); + resource.setAccessTokenUri(AUTHORIZATION_SERVER + "/oauth/token"); + resource.setClientId("oauth2"); + resource.setClientSecret("oauth2"); + resource.setId("oauth2"); + resource.setScope(scope); + resource.setUsername(username); + resource.setPassword(password); + return new OAuth2RestTemplate(resource); + } + + @Test + void testAccessTokenWhenPassed() { + assertNotNull(oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")).getAccessToken()); + } +} diff --git a/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java b/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java new file mode 100644 index 0000000..85090f4 --- /dev/null +++ b/demo-oauth/oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java @@ -0,0 +1,79 @@ +package com.xkcoding.oauth.controller; + +import com.xkcoding.oauth.AuthorizationTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.springframework.http.HttpMethod.GET; + +/** + * . + * + * @author EchoCow + * @date 2020-01-09 15:46 + */ +public class TestControllerTest extends AuthorizationTest { + + private static final String URL = "http://127.0.0.1:8081"; + + @Test + @DisplayName("ROLE_ADMIN 角色测试") + void testAdminRoleSucceedAndTestRoleFailedWhenPassed() { + OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")); + ResponseEntity response = template.exchange(URL + "/admin", GET, null, String.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("ADMIN", response.getBody()); + assertThrows(OAuth2AccessDeniedException.class, () -> template.exchange(URL + "/test", GET, null, String.class)); + } + + @Test + @DisplayName("ROLE_Test 角色测试") + void testTestRoleSucceedWhenPassed() { + OAuth2RestTemplate template = oauth2RestTemplate("test", "123456", Collections.singletonList("READ")); + ResponseEntity response = template.exchange(URL + "/test", GET, null, String.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("TEST", response.getBody()); + assertThrows(OAuth2AccessDeniedException.class, () -> template.exchange(URL + "/admin", GET, null, String.class)); + } + + @Test + @DisplayName("SCOPE_READ 授权域测试") + void testScopeReadWhenPassed() { + OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")); + ResponseEntity response = template.exchange(URL + "/read", GET, null, String.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("READ", response.getBody()); + assertThrows(OAuth2AccessDeniedException.class, () -> template.exchange(URL + "/write", GET, null, String.class)); + } + + @Test + @DisplayName("SCOPE_WRITE 授权域测试") + void testScopeWriteWhenPassed() { + OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("WRITE")); + ResponseEntity response = template.exchange(URL + "/write", GET, null, String.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("WRITE", response.getBody()); + assertThrows(OAuth2AccessDeniedException.class, () -> template.exchange(URL + "/read", GET, null, String.class)); + } + + @Test + @DisplayName("SCOPE 测试") + void testScopeWhenPassed() { + OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Arrays.asList("READ", "WRITE")); + ResponseEntity writeResponse = template.exchange(URL + "/write", GET, null, String.class); + assertEquals(HttpStatus.OK, writeResponse.getStatusCode()); + assertEquals("WRITE", writeResponse.getBody()); + ResponseEntity readResponse = template.exchange(URL + "/read", GET, null, String.class); + assertEquals(HttpStatus.OK, readResponse.getStatusCode()); + assertEquals("READ", readResponse.getBody()); + } +} diff --git a/demo-oauth/pom.xml b/demo-oauth/pom.xml new file mode 100644 index 0000000..44aeb5f --- /dev/null +++ b/demo-oauth/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + demo-oauth + 1.0.0-SNAPSHOT + + oauth-authorization-server + oauth-resource-server + + pom + + demo-oauth + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + mysql + mysql-connector-java + runtime + + + + com.h2database + h2 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + + + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + org.junit.jupiter + junit-jupiter + 5.5.2 + test + + + + + + demo-oauth + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-orm-beetlsql/.gitignore b/demo-orm-beetlsql/.gitignore similarity index 100% rename from spring-boot-demo-orm-beetlsql/.gitignore rename to demo-orm-beetlsql/.gitignore diff --git a/demo-orm-beetlsql/README.md b/demo-orm-beetlsql/README.md new file mode 100644 index 0000000..1eb3827 --- /dev/null +++ b/demo-orm-beetlsql/README.md @@ -0,0 +1,368 @@ +# spring-boot-demo-orm-beetlsql + +> 此 demo 主要演示了 Spring Boot 如何整合 beetl sql 快捷操作数据库,使用的是beetl官方提供的beetl-framework-starter集成。集成过程不是十分顺利,没有其他的orm框架集成的便捷。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-beetlsql + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-beetlsql + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.68.RELEASE + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + com.ibeetl + beetl-framework-starter + ${ibeetl.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-orm-beetlsql + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +> 注意下方注释的地方,**不能解开注释,并且需要通过JavaConfig的方式手动配置数据源**,否则,会导致beetl启动失败,因此,初始化数据库的数据,只能手动在数据库使用 resources/db 下的建表语句和数据库初始化数据。 + +```yaml +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver +#### beetlsql starter不能开启下面选项 +# type: com.zaxxer.hikari.HikariDataSource +# initialization-mode: always +# continue-on-error: true +# schema: +# - "classpath:db/schema.sql" +# data: +# - "classpath:db/data.sql" +# hikari: +# minimum-idle: 5 +# connection-test-query: SELECT 1 FROM DUAL +# maximum-pool-size: 20 +# auto-commit: true +# idle-timeout: 30000 +# pool-name: SpringBootDemoHikariCP +# max-lifetime: 60000 +# connection-timeout: 30000 +logging: + level: + com.xkcoding: debug + com.xkcoding.orm.beetlsql: trace +beetl: + enabled: false +beetlsql: + enabled: true + sqlPath: /sql + daoSuffix: Dao + basePackage: com.xkcoding.orm.beetlsql.dao + dbStyle: org.beetl.sql.core.db.MySqlStyle + nameConversion: org.beetl.sql.core.UnderlinedNameConversion +beet-beetlsql: + dev: true +``` + +## BeetlConfig.java + +```java +/** + *

    + * Beetl数据源配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 17:15 + */ +@Configuration +public class BeetlConfig { + + /** + * Beetl需要显示的配置数据源,方可启动项目,大坑,切记! + */ + @Bean(name = "datasource") + public DataSource getDataSource(Environment env){ + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name")); + dataSource.setJdbcUrl(env.getProperty("spring.datasource.url")); + dataSource.setUsername(env.getProperty("spring.datasource.username")); + dataSource.setPassword(env.getProperty("spring.datasource.password")); + return dataSource; + } +} +``` + +## UserDao.java + +```java +/** + *

    + * UserDao + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:18 + */ +@Component +public interface UserDao extends BaseMapper { + +} +``` + +## UserServiceImpl.java + +```java +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:28 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + + private final UserDao userDao; + + @Autowired + public UserServiceImpl(UserDao userDao) { + this.userDao = userDao; + } + + /** + * 新增用户 + * + * @param user 用户 + */ + @Override + public User saveUser(User user) { + userDao.insert(user, true); + return user; + } + + /** + * 批量插入用户 + * + * @param users 用户列表 + */ + @Override + public void saveUserList(List users) { + userDao.insertBatch(users); + } + + /** + * 根据主键删除用户 + * + * @param id 主键 + */ + @Override + public void deleteUser(Long id) { + userDao.deleteById(id); + } + + /** + * 更新用户 + * + * @param user 用户 + * @return 更新后的用户 + */ + @Override + public User updateUser(User user) { + if (ObjectUtil.isNull(user)) { + throw new RuntimeException("用户id不能为null"); + } + userDao.updateTemplateById(user); + return userDao.single(user.getId()); + } + + /** + * 查询单个用户 + * + * @param id 主键id + * @return 用户信息 + */ + @Override + public User getUser(Long id) { + return userDao.single(id); + } + + /** + * 查询用户列表 + * + * @return 用户列表 + */ + @Override + public List getUserList() { + return userDao.all(); + } + + /** + * 分页查询 + * + * @param currentPage 当前页 + * @param pageSize 每页条数 + * @return 分页用户列表 + */ + @Override + public PageQuery getUserByPage(Integer currentPage, Integer pageSize) { + return userDao.createLambdaQuery().page(currentPage, pageSize); + } +} +``` + +## UserServiceTest.java + +```java +/** + *

    + * User Service测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:30 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoOrmBeetlsqlApplicationTests { + @Autowired + private UserService userService; + + @Test + public void saveUser() { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + + user = userService.saveUser(user); + Assert.assertTrue(ObjectUtil.isNotNull(user.getId())); + log.debug("【user】= {}", user); + } + + @Test + public void saveUserList() { + List users = Lists.newArrayList(); + for (int i = 5; i < 15; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + users.add(user); + } + userService.saveUserList(users); + Assert.assertTrue(userService.getUserList().size() > 2); + } + + @Test + public void deleteUser() { + userService.deleteUser(1L); + User user = userService.getUser(1L); + Assert.assertTrue(ObjectUtil.isNull(user)); + } + + @Test + public void updateUser() { + User user = userService.getUser(2L); + user.setName("beetlSql 修改后的名字"); + User update = userService.updateUser(user); + Assert.assertEquals("beetlSql 修改后的名字", update.getName()); + log.debug("【update】= {}", update); + } + + @Test + public void getUser() { + User user = userService.getUser(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + @Test + public void getUserList() { + List userList = userService.getUserList(); + Assert.assertTrue(CollUtil.isNotEmpty(userList)); + log.debug("【userList】= {}", userList); + } + + @Test + public void getUserByPage() { + List userList = userService.getUserList(); + PageQuery userByPage = userService.getUserByPage(1, 5); + Assert.assertEquals(5, userByPage.getList().size()); + Assert.assertEquals(userList.size(), userByPage.getTotalRow()); + log.debug("【userByPage】= {}", JSONUtil.toJsonStr(userByPage)); + } +} +``` + +## 参考 + +- BeetlSQL官方文档:http://ibeetl.com/guide/#beetlsql +- 开源项目:https://gitee.com/yangkb/springboot-beetl-beetlsql +- 博客:https://blog.csdn.net/flystarfly/article/details/82752597 diff --git a/demo-orm-beetlsql/pom.xml b/demo-orm-beetlsql/pom.xml new file mode 100644 index 0000000..427925b --- /dev/null +++ b/demo-orm-beetlsql/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + demo-orm-beetlsql + 1.0.0-SNAPSHOT + jar + + demo-orm-beetlsql + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.68.RELEASE + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + com.ibeetl + beetl-framework-starter + ${ibeetl.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-orm-beetlsql + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java new file mode 100644 index 0000000..62662fc --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.orm.beetlsql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 15:47 + */ +@SpringBootApplication +public class SpringBootDemoOrmBeetlsqlApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmBeetlsqlApplication.class, args); + } +} diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java new file mode 100644 index 0000000..e070a72 --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java @@ -0,0 +1,33 @@ +package com.xkcoding.orm.beetlsql.config; + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import javax.sql.DataSource; + +/** + *

    + * Beetl数据源配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 17:15 + */ +@Configuration +public class BeetlConfig { + + /** + * Beetl需要显示的配置数据源,方可启动项目,大坑,切记! + */ + @Bean(name = "datasource") + public DataSource getDataSource(Environment env) { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name")); + dataSource.setJdbcUrl(env.getProperty("spring.datasource.url")); + dataSource.setUsername(env.getProperty("spring.datasource.username")); + dataSource.setPassword(env.getProperty("spring.datasource.password")); + return dataSource; + } +} diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java new file mode 100644 index 0000000..8dda597 --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java @@ -0,0 +1,18 @@ +package com.xkcoding.orm.beetlsql.dao; + +import com.xkcoding.orm.beetlsql.entity.User; +import org.beetl.sql.core.mapper.BaseMapper; +import org.springframework.stereotype.Component; + +/** + *

    + * UserDao + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:18 + */ +@Component +public interface UserDao extends BaseMapper { + +} diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java new file mode 100644 index 0000000..9a4aadb --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java @@ -0,0 +1,77 @@ +package com.xkcoding.orm.beetlsql.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.beetl.sql.core.annotatoin.Table; + +import java.io.Serializable; +import java.util.Date; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:06 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "orm_user") +public class User implements Serializable { + private static final long serialVersionUID = -1840831686851699943L; + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 上次登录时间 + */ + private Date lastLoginTime; + + /** + * 上次更新时间 + */ + private Date lastUpdateTime; +} diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java new file mode 100644 index 0000000..1539288 --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java @@ -0,0 +1,71 @@ +package com.xkcoding.orm.beetlsql.service; + +import com.xkcoding.orm.beetlsql.entity.User; +import org.beetl.sql.core.engine.PageQuery; + +import java.util.List; + +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:18 + */ +public interface UserService { + /** + * 新增用户 + * + * @param user 用户 + * @return 保存的用户 + */ + User saveUser(User user); + + + /** + * 批量插入用户 + * + * @param users 用户列表 + */ + void saveUserList(List users); + + /** + * 根据主键删除用户 + * + * @param id 主键 + */ + void deleteUser(Long id); + + /** + * 更新用户 + * + * @param user 用户 + * @return 更新后的用户 + */ + User updateUser(User user); + + /** + * 查询单个用户 + * + * @param id 主键id + * @return 用户信息 + */ + User getUser(Long id); + + /** + * 查询用户列表 + * + * @return 用户列表 + */ + List getUserList(); + + /** + * 分页查询 + * + * @param currentPage 当前页 + * @param pageSize 每页条数 + * @return 分页用户列表 + */ + PageQuery getUserByPage(Integer currentPage, Integer pageSize); +} diff --git a/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..7ab1f7e --- /dev/null +++ b/demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java @@ -0,0 +1,111 @@ +package com.xkcoding.orm.beetlsql.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.orm.beetlsql.dao.UserDao; +import com.xkcoding.orm.beetlsql.entity.User; +import com.xkcoding.orm.beetlsql.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.beetl.sql.core.engine.PageQuery; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:28 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + + private final UserDao userDao; + + @Autowired + public UserServiceImpl(UserDao userDao) { + this.userDao = userDao; + } + + /** + * 新增用户 + * + * @param user 用户 + */ + @Override + public User saveUser(User user) { + userDao.insert(user, true); + return user; + } + + /** + * 批量插入用户 + * + * @param users 用户列表 + */ + @Override + public void saveUserList(List users) { + userDao.insertBatch(users); + } + + /** + * 根据主键删除用户 + * + * @param id 主键 + */ + @Override + public void deleteUser(Long id) { + userDao.deleteById(id); + } + + /** + * 更新用户 + * + * @param user 用户 + * @return 更新后的用户 + */ + @Override + public User updateUser(User user) { + if (ObjectUtil.isNull(user)) { + throw new RuntimeException("用户id不能为null"); + } + userDao.updateTemplateById(user); + return userDao.single(user.getId()); + } + + /** + * 查询单个用户 + * + * @param id 主键id + * @return 用户信息 + */ + @Override + public User getUser(Long id) { + return userDao.single(id); + } + + /** + * 查询用户列表 + * + * @return 用户列表 + */ + @Override + public List getUserList() { + return userDao.all(); + } + + /** + * 分页查询 + * + * @param currentPage 当前页 + * @param pageSize 每页条数 + * @return 分页用户列表 + */ + @Override + public PageQuery getUserByPage(Integer currentPage, Integer pageSize) { + return userDao.createLambdaQuery().page(currentPage, pageSize); + } +} diff --git a/spring-boot-demo-orm-beetlsql/src/main/resources/application.yml b/demo-orm-beetlsql/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-beetlsql/src/main/resources/application.yml rename to demo-orm-beetlsql/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-beetlsql/src/main/resources/db/data.sql b/demo-orm-beetlsql/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-beetlsql/src/main/resources/db/data.sql rename to demo-orm-beetlsql/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-beetlsql/src/main/resources/db/schema.sql b/demo-orm-beetlsql/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-beetlsql/src/main/resources/db/schema.sql rename to demo-orm-beetlsql/src/main/resources/db/schema.sql diff --git a/spring-boot-demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplicationTests.java b/demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplicationTests.java similarity index 100% rename from spring-boot-demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplicationTests.java rename to demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplicationTests.java diff --git a/demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java b/demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java new file mode 100644 index 0000000..50e6363 --- /dev/null +++ b/demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java @@ -0,0 +1,93 @@ +package com.xkcoding.orm.beetlsql.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.orm.beetlsql.SpringBootDemoOrmBeetlsqlApplicationTests; +import com.xkcoding.orm.beetlsql.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.beetl.sql.core.engine.PageQuery; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + *

    + * User Service测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-14 16:30 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoOrmBeetlsqlApplicationTests { + @Autowired + private UserService userService; + + @Test + public void saveUser() { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + + user = userService.saveUser(user); + Assert.assertTrue(ObjectUtil.isNotNull(user.getId())); + log.debug("【user】= {}", user); + } + + @Test + public void saveUserList() { + List users = Lists.newArrayList(); + for (int i = 5; i < 15; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + users.add(user); + } + userService.saveUserList(users); + Assert.assertTrue(userService.getUserList().size() > 2); + } + + @Test + public void deleteUser() { + userService.deleteUser(1L); + User user = userService.getUser(1L); + Assert.assertTrue(ObjectUtil.isNull(user)); + } + + @Test + public void updateUser() { + User user = userService.getUser(2L); + user.setName("beetlSql 修改后的名字"); + User update = userService.updateUser(user); + Assert.assertEquals("beetlSql 修改后的名字", update.getName()); + log.debug("【update】= {}", update); + } + + @Test + public void getUser() { + User user = userService.getUser(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + @Test + public void getUserList() { + List userList = userService.getUserList(); + Assert.assertTrue(CollUtil.isNotEmpty(userList)); + log.debug("【userList】= {}", userList); + } + + @Test + public void getUserByPage() { + List userList = userService.getUserList(); + PageQuery userByPage = userService.getUserByPage(1, 5); + Assert.assertEquals(5, userByPage.getList().size()); + Assert.assertEquals(userList.size(), userByPage.getTotalRow()); + log.debug("【userByPage】= {}", JSONUtil.toJsonStr(userByPage)); + } +} diff --git a/spring-boot-demo-orm-jdbctemplate/.gitignore b/demo-orm-jdbctemplate/.gitignore similarity index 100% rename from spring-boot-demo-orm-jdbctemplate/.gitignore rename to demo-orm-jdbctemplate/.gitignore diff --git a/demo-orm-jdbctemplate/README.md b/demo-orm-jdbctemplate/README.md new file mode 100644 index 0000000..b170dd7 --- /dev/null +++ b/demo-orm-jdbctemplate/README.md @@ -0,0 +1,327 @@ +# spring-boot-demo-orm-jdbctemplate +> 本 demo 主要演示了Spring Boot如何使用 JdbcTemplate 操作数据库,并且简易地封装了一个通用的 Dao 层,包括增删改查。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-jdbctemplate + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-jdbctemplate + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-orm-jdbctemplate + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## BaseDao.java + +```java +/** + *

    + * Dao基类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:28 + */ +@Slf4j +public class BaseDao { + private JdbcTemplate jdbcTemplate; + private Class clazz; + + @SuppressWarnings(value = "unchecked") + public BaseDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } + + /** + * 通用插入,自增列需要添加 {@link Pk} 注解 + * + * @param t 对象 + * @param ignoreNull 是否忽略 null 值 + * @return 操作的行数 + */ + protected Integer insert(T t, Boolean ignoreNull) { + String table = getTableName(t); + + List filterField = getField(t, ignoreNull); + + List columnList = getColumns(filterField); + + String columns = StrUtil.join(Const.SEPARATOR_COMMA, columnList); + + // 构造占位符 + String params = StrUtil.repeatAndJoin("?", columnList.size(), Const.SEPARATOR_COMMA); + + // 构造值 + Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); + + String sql = StrUtil.format("INSERT INTO {table} ({columns}) VALUES ({params})", Dict.create().set("table", table).set("columns", columns).set("params", params)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + return jdbcTemplate.update(sql, values); + } + + /** + * 通用根据主键删除 + * + * @param pk 主键 + * @return 影响行数 + */ + protected Integer deleteById(P pk) { + String tableName = getTableName(); + String sql = StrUtil.format("DELETE FROM {table} where id = ?", Dict.create().set("table", tableName)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); + return jdbcTemplate.update(sql, pk); + } + + /** + * 通用根据主键更新,自增列需要添加 {@link Pk} 注解 + * + * @param t 对象 + * @param pk 主键 + * @param ignoreNull 是否忽略 null 值 + * @return 操作的行数 + */ + protected Integer updateById(T t, P pk, Boolean ignoreNull) { + String tableName = getTableName(t); + + List filterField = getField(t, ignoreNull); + + List columnList = getColumns(filterField); + + List columns = columnList.stream().map(s -> StrUtil.appendIfMissing(s, " = ?")).collect(Collectors.toList()); + String params = StrUtil.join(Const.SEPARATOR_COMMA, columns); + + // 构造值 + List valueList = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).collect(Collectors.toList()); + valueList.add(pk); + + Object[] values = ArrayUtil.toArray(valueList, Object.class); + + String sql = StrUtil.format("UPDATE {table} SET {params} where id = ?", Dict.create().set("table", tableName).set("params", params)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + return jdbcTemplate.update(sql, values); + } + + /** + * 通用根据主键查询单条记录 + * + * @param pk 主键 + * @return 单条记录 + */ + public T findOneById(P pk) { + String tableName = getTableName(); + String sql = StrUtil.format("SELECT * FROM {table} where id = ?", Dict.create().set("table", tableName)); + RowMapper rowMapper = new BeanPropertyRowMapper<>(clazz); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); + return jdbcTemplate.queryForObject(sql, new Object[]{pk}, rowMapper); + } + + /** + * 根据对象查询 + * + * @param t 查询条件 + * @return 对象列表 + */ + public List findByExample(T t) { + String tableName = getTableName(t); + List filterField = getField(t, true); + List columnList = getColumns(filterField); + + List columns = columnList.stream().map(s -> " and " + s + " = ? ").collect(Collectors.toList()); + + String where = StrUtil.join(" ", columns); + // 构造值 + Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); + + String sql = StrUtil.format("SELECT * FROM {table} where 1=1 {where}", Dict.create().set("table", tableName).set("where", StrUtil.isBlank(where) ? "" : where)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + List> maps = jdbcTemplate.queryForList(sql, values); + List ret = CollUtil.newArrayList(); + maps.forEach(map -> ret.add(BeanUtil.fillBeanWithMap(map, ReflectUtil.newInstance(clazz), true, false))); + return ret; + } + + /** + * 获取表名 + * + * @param t 对象 + * @return 表名 + */ + private String getTableName(T t) { + Table tableAnnotation = t.getClass().getAnnotation(Table.class); + if (ObjectUtil.isNotNull(tableAnnotation)) { + return StrUtil.format("`{}`", tableAnnotation.name()); + } else { + return StrUtil.format("`{}`", t.getClass().getName().toLowerCase()); + } + } + + /** + * 获取表名 + * + * @return 表名 + */ + private String getTableName() { + Table tableAnnotation = clazz.getAnnotation(Table.class); + if (ObjectUtil.isNotNull(tableAnnotation)) { + return StrUtil.format("`{}`", tableAnnotation.name()); + } else { + return StrUtil.format("`{}`", clazz.getName().toLowerCase()); + } + } + + /** + * 获取列 + * + * @param fieldList 字段列表 + * @return 列信息列表 + */ + private List getColumns(List fieldList) { + // 构造列 + List columnList = CollUtil.newArrayList(); + for (Field field : fieldList) { + Column columnAnnotation = field.getAnnotation(Column.class); + String columnName; + if (ObjectUtil.isNotNull(columnAnnotation)) { + columnName = columnAnnotation.name(); + } else { + columnName = field.getName(); + } + columnList.add(StrUtil.format("`{}`", columnName)); + } + return columnList; + } + + /** + * 获取字段列表 {@code 过滤数据库中不存在的字段,以及自增列} + * + * @param t 对象 + * @param ignoreNull 是否忽略空值 + * @return 字段列表 + */ + private List getField(T t, Boolean ignoreNull) { + // 获取所有字段,包含父类中的字段 + Field[] fields = ReflectUtil.getFields(t.getClass()); + + // 过滤数据库中不存在的字段,以及自增列 + List filterField; + Stream fieldStream = CollUtil.toList(fields).stream().filter(field -> ObjectUtil.isNull(field.getAnnotation(Ignore.class)) || ObjectUtil.isNull(field.getAnnotation(Pk.class))); + + // 是否过滤字段值为null的字段 + if (ignoreNull) { + filterField = fieldStream.filter(field -> ObjectUtil.isNotNull(ReflectUtil.getFieldValue(t, field))).collect(Collectors.toList()); + } else { + filterField = fieldStream.collect(Collectors.toList()); + } + return filterField; + } + +} +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + initialization-mode: always + continue-on-error: true + schema: + - "classpath:db/schema.sql" + data: + - "classpath:db/data.sql" + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SpringBootDemoHikariCP + max-lifetime: 60000 + connection-timeout: 30000 +logging: + level: + com.xkcoding: debug +``` + +## 备注 + +其余详细代码参见 demo diff --git a/demo-orm-jdbctemplate/pom.xml b/demo-orm-jdbctemplate/pom.xml new file mode 100644 index 0000000..f4e8a16 --- /dev/null +++ b/demo-orm-jdbctemplate/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-orm-jdbctemplate + 1.0.0-SNAPSHOT + jar + + demo-orm-jdbctemplate + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + demo-orm-jdbctemplate + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java new file mode 100644 index 0000000..dba7b65 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.orm.jdbctemplate; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 9:50 + */ +@SpringBootApplication +public class SpringBootDemoOrmJdbctemplateApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmJdbctemplateApplication.class, args); + } +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java new file mode 100644 index 0000000..0b77cdd --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java @@ -0,0 +1,25 @@ +package com.xkcoding.orm.jdbctemplate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

    + * 列注解 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:23 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Column { + /** + * 列名 + * + * @return 列名 + */ + String name(); +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java new file mode 100644 index 0000000..fba3dc5 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java @@ -0,0 +1,19 @@ +package com.xkcoding.orm.jdbctemplate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

    + * 需要忽略的字段 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 13:25 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Ignore { +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java new file mode 100644 index 0000000..53c4cac --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java @@ -0,0 +1,25 @@ +package com.xkcoding.orm.jdbctemplate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

    + * 主键注解 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:23 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Pk { + /** + * 自增 + * + * @return 自增主键 + */ + boolean auto() default true; +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java new file mode 100644 index 0000000..60528a8 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java @@ -0,0 +1,25 @@ +package com.xkcoding.orm.jdbctemplate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

    + * 表注解 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:23 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Table { + /** + * 表名 + * + * @return 表名 + */ + String name(); +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java new file mode 100644 index 0000000..b05bedb --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java @@ -0,0 +1,21 @@ +package com.xkcoding.orm.jdbctemplate.constant; + +/** + *

    + * 常量池 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 10:59 + */ +public interface Const { + /** + * 加密盐前缀 + */ + String SALT_PREFIX = "::SpringBootDemo::"; + + /** + * 逗号分隔符 + */ + String SEPARATOR_COMMA = ","; +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java new file mode 100644 index 0000000..4171ce2 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java @@ -0,0 +1,59 @@ +package com.xkcoding.orm.jdbctemplate.controller; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.orm.jdbctemplate.entity.User; +import com.xkcoding.orm.jdbctemplate.service.IUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + *

    + * User Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 13:58 + */ +@RestController +@Slf4j +public class UserController { + private final IUserService userService; + + @Autowired + public UserController(IUserService userService) { + this.userService = userService; + } + + @PostMapping("/user") + public Dict save(@RequestBody User user) { + Boolean save = userService.save(user); + return Dict.create().set("code", save ? 200 : 500).set("msg", save ? "成功" : "失败").set("data", save ? user : null); + } + + @DeleteMapping("/user/{id}") + public Dict delete(@PathVariable Long id) { + Boolean delete = userService.delete(id); + return Dict.create().set("code", delete ? 200 : 500).set("msg", delete ? "成功" : "失败"); + } + + @PutMapping("/user/{id}") + public Dict update(@RequestBody User user, @PathVariable Long id) { + Boolean update = userService.update(user, id); + return Dict.create().set("code", update ? 200 : 500).set("msg", update ? "成功" : "失败").set("data", update ? user : null); + } + + @GetMapping("/user/{id}") + public Dict getUser(@PathVariable Long id) { + User user = userService.getUser(id); + return Dict.create().set("code", 200).set("msg", "成功").set("data", user); + } + + @GetMapping("/user") + public Dict getUser(User user) { + List userList = userService.getUser(user); + return Dict.create().set("code", 200).set("msg", "成功").set("data", userList); + } +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java new file mode 100644 index 0000000..a1b0fbf --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java @@ -0,0 +1,77 @@ +package com.xkcoding.orm.jdbctemplate.dao; + +import com.xkcoding.orm.jdbctemplate.dao.base.BaseDao; +import com.xkcoding.orm.jdbctemplate.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + *

    + * User Dao + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:15 + */ +@Repository +public class UserDao extends BaseDao { + + @Autowired + public UserDao(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + /** + * 保存用户 + * + * @param user 用户对象 + * @return 操作影响行数 + */ + public Integer insert(User user) { + return super.insert(user, true); + } + + /** + * 根据主键删除用户 + * + * @param id 主键id + * @return 操作影响行数 + */ + public Integer delete(Long id) { + return super.deleteById(id); + } + + /** + * 更新用户 + * + * @param user 用户对象 + * @param id 主键id + * @return 操作影响行数 + */ + public Integer update(User user, Long id) { + return super.updateById(user, id, true); + } + + /** + * 根据主键获取用户 + * + * @param id 主键id + * @return id对应的用户 + */ + public User selectById(Long id) { + return super.findOneById(id); + } + + /** + * 根据查询条件获取用户列表 + * + * @param user 用户查询条件 + * @return 用户列表 + */ + public List selectUserList(User user) { + return super.findByExample(user); + } +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java new file mode 100644 index 0000000..563e7bd --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java @@ -0,0 +1,235 @@ +package com.xkcoding.orm.jdbctemplate.dao.base; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.orm.jdbctemplate.annotation.Column; +import com.xkcoding.orm.jdbctemplate.annotation.Ignore; +import com.xkcoding.orm.jdbctemplate.annotation.Pk; +import com.xkcoding.orm.jdbctemplate.annotation.Table; +import com.xkcoding.orm.jdbctemplate.constant.Const; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

    + * Dao基类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 11:28 + */ +@Slf4j +public class BaseDao { + private JdbcTemplate jdbcTemplate; + private Class clazz; + + @SuppressWarnings(value = "unchecked") + public BaseDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } + + /** + * 通用插入,自增列需要添加 {@link Pk} 注解 + * + * @param t 对象 + * @param ignoreNull 是否忽略 null 值 + * @return 操作的行数 + */ + protected Integer insert(T t, Boolean ignoreNull) { + String table = getTableName(t); + + List filterField = getField(t, ignoreNull); + + List columnList = getColumns(filterField); + + String columns = StrUtil.join(Const.SEPARATOR_COMMA, columnList); + + // 构造占位符 + String params = StrUtil.repeatAndJoin("?", columnList.size(), Const.SEPARATOR_COMMA); + + // 构造值 + Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); + + String sql = StrUtil.format("INSERT INTO {table} ({columns}) VALUES ({params})", Dict.create().set("table", table).set("columns", columns).set("params", params)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + return jdbcTemplate.update(sql, values); + } + + /** + * 通用根据主键删除 + * + * @param pk 主键 + * @return 影响行数 + */ + protected Integer deleteById(P pk) { + String tableName = getTableName(); + String sql = StrUtil.format("DELETE FROM {table} where id = ?", Dict.create().set("table", tableName)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); + return jdbcTemplate.update(sql, pk); + } + + /** + * 通用根据主键更新,自增列需要添加 {@link Pk} 注解 + * + * @param t 对象 + * @param pk 主键 + * @param ignoreNull 是否忽略 null 值 + * @return 操作的行数 + */ + protected Integer updateById(T t, P pk, Boolean ignoreNull) { + String tableName = getTableName(t); + + List filterField = getField(t, ignoreNull); + + List columnList = getColumns(filterField); + + List columns = columnList.stream().map(s -> StrUtil.appendIfMissing(s, " = ?")).collect(Collectors.toList()); + String params = StrUtil.join(Const.SEPARATOR_COMMA, columns); + + // 构造值 + List valueList = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).collect(Collectors.toList()); + valueList.add(pk); + + Object[] values = ArrayUtil.toArray(valueList, Object.class); + + String sql = StrUtil.format("UPDATE {table} SET {params} where id = ?", Dict.create().set("table", tableName).set("params", params)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + return jdbcTemplate.update(sql, values); + } + + /** + * 通用根据主键查询单条记录 + * + * @param pk 主键 + * @return 单条记录 + */ + public T findOneById(P pk) { + String tableName = getTableName(); + String sql = StrUtil.format("SELECT * FROM {table} where id = ?", Dict.create().set("table", tableName)); + RowMapper rowMapper = new BeanPropertyRowMapper<>(clazz); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); + return jdbcTemplate.queryForObject(sql, new Object[]{pk}, rowMapper); + } + + /** + * 根据对象查询 + * + * @param t 查询条件 + * @return 对象列表 + */ + public List findByExample(T t) { + String tableName = getTableName(t); + List filterField = getField(t, true); + List columnList = getColumns(filterField); + + List columns = columnList.stream().map(s -> " and " + s + " = ? ").collect(Collectors.toList()); + + String where = StrUtil.join(" ", columns); + // 构造值 + Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); + + String sql = StrUtil.format("SELECT * FROM {table} where 1=1 {where}", Dict.create().set("table", tableName).set("where", StrUtil.isBlank(where) ? "" : where)); + log.debug("【执行SQL】SQL:{}", sql); + log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); + List> maps = jdbcTemplate.queryForList(sql, values); + List ret = CollUtil.newArrayList(); + maps.forEach(map -> ret.add(BeanUtil.fillBeanWithMap(map, ReflectUtil.newInstance(clazz), true, false))); + return ret; + } + + /** + * 获取表名 + * + * @param t 对象 + * @return 表名 + */ + private String getTableName(T t) { + Table tableAnnotation = t.getClass().getAnnotation(Table.class); + if (ObjectUtil.isNotNull(tableAnnotation)) { + return StrUtil.format("`{}`", tableAnnotation.name()); + } else { + return StrUtil.format("`{}`", t.getClass().getName().toLowerCase()); + } + } + + /** + * 获取表名 + * + * @return 表名 + */ + private String getTableName() { + Table tableAnnotation = clazz.getAnnotation(Table.class); + if (ObjectUtil.isNotNull(tableAnnotation)) { + return StrUtil.format("`{}`", tableAnnotation.name()); + } else { + return StrUtil.format("`{}`", clazz.getName().toLowerCase()); + } + } + + /** + * 获取列 + * + * @param fieldList 字段列表 + * @return 列信息列表 + */ + private List getColumns(List fieldList) { + // 构造列 + List columnList = CollUtil.newArrayList(); + for (Field field : fieldList) { + Column columnAnnotation = field.getAnnotation(Column.class); + String columnName; + if (ObjectUtil.isNotNull(columnAnnotation)) { + columnName = columnAnnotation.name(); + } else { + columnName = field.getName(); + } + columnList.add(StrUtil.format("`{}`", columnName)); + } + return columnList; + } + + /** + * 获取字段列表 {@code 过滤数据库中不存在的字段,以及自增列} + * + * @param t 对象 + * @param ignoreNull 是否忽略空值 + * @return 字段列表 + */ + private List getField(T t, Boolean ignoreNull) { + // 获取所有字段,包含父类中的字段 + Field[] fields = ReflectUtil.getFields(t.getClass()); + + // 过滤数据库中不存在的字段,以及自增列 + List filterField; + Stream fieldStream = CollUtil.toList(fields).stream().filter(field -> ObjectUtil.isNull(field.getAnnotation(Ignore.class)) || ObjectUtil.isNull(field.getAnnotation(Pk.class))); + + // 是否过滤字段值为null的字段 + if (ignoreNull) { + filterField = fieldStream.filter(field -> ObjectUtil.isNotNull(ReflectUtil.getFieldValue(t, field))).collect(Collectors.toList()); + } else { + filterField = fieldStream.collect(Collectors.toList()); + } + return filterField; + } + +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java new file mode 100644 index 0000000..57cdb00 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java @@ -0,0 +1,76 @@ +package com.xkcoding.orm.jdbctemplate.entity; + +import com.xkcoding.orm.jdbctemplate.annotation.Column; +import com.xkcoding.orm.jdbctemplate.annotation.Pk; +import com.xkcoding.orm.jdbctemplate.annotation.Table; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 10:45 + */ +@Data +@Table(name = "orm_user") +public class User implements Serializable { + /** + * 主键 + */ + @Pk + private Long id; + + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + @Column(name = "phone_number") + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + @Column(name = "create_time") + private Date createTime; + + /** + * 上次登录时间 + */ + @Column(name = "last_login_time") + private Date lastLoginTime; + + /** + * 上次更新时间 + */ + @Column(name = "last_update_time") + private Date lastUpdateTime; +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java new file mode 100644 index 0000000..6db2051 --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java @@ -0,0 +1,57 @@ +package com.xkcoding.orm.jdbctemplate.service; + +import com.xkcoding.orm.jdbctemplate.entity.User; + +import java.util.List; + +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 13:51 + */ +public interface IUserService { + /** + * 保存用户 + * + * @param user 用户实体 + * @return 保存成功 {@code true} 保存失败 {@code false} + */ + Boolean save(User user); + + /** + * 删除用户 + * + * @param id 主键id + * @return 删除成功 {@code true} 删除失败 {@code false} + */ + Boolean delete(Long id); + + /** + * 更新用户 + * + * @param user 用户实体 + * @param id 主键id + * @return 更新成功 {@code true} 更新失败 {@code false} + */ + Boolean update(User user, Long id); + + /** + * 获取单个用户 + * + * @param id 主键id + * @return 单个用户对象 + */ + User getUser(Long id); + + /** + * 获取用户列表 + * + * @param user 用户实体 + * @return 用户列表 + */ + List getUser(User user); + +} diff --git a/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..beee4fc --- /dev/null +++ b/demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java @@ -0,0 +1,105 @@ +package com.xkcoding.orm.jdbctemplate.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.xkcoding.orm.jdbctemplate.constant.Const; +import com.xkcoding.orm.jdbctemplate.dao.UserDao; +import com.xkcoding.orm.jdbctemplate.entity.User; +import com.xkcoding.orm.jdbctemplate.service.IUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

    + * User Service Implement + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-15 13:53 + */ +@Service +public class UserServiceImpl implements IUserService { + private final UserDao userDao; + + @Autowired + public UserServiceImpl(UserDao userDao) { + this.userDao = userDao; + } + + /** + * 保存用户 + * + * @param user 用户实体 + * @return 保存成功 {@code true} 保存失败 {@code false} + */ + @Override + public Boolean save(User user) { + String rawPass = user.getPassword(); + String salt = IdUtil.simpleUUID(); + String pass = SecureUtil.md5(rawPass + Const.SALT_PREFIX + salt); + user.setPassword(pass); + user.setSalt(salt); + return userDao.insert(user) > 0; + } + + /** + * 删除用户 + * + * @param id 主键id + * @return 删除成功 {@code true} 删除失败 {@code false} + */ + @Override + public Boolean delete(Long id) { + return userDao.delete(id) > 0; + } + + /** + * 更新用户 + * + * @param user 用户实体 + * @param id 主键id + * @return 更新成功 {@code true} 更新失败 {@code false} + */ + @Override + public Boolean update(User user, Long id) { + User exist = getUser(id); + if (StrUtil.isNotBlank(user.getPassword())) { + String rawPass = user.getPassword(); + String salt = IdUtil.simpleUUID(); + String pass = SecureUtil.md5(rawPass + Const.SALT_PREFIX + salt); + user.setPassword(pass); + user.setSalt(salt); + } + BeanUtil.copyProperties(user, exist, CopyOptions.create().setIgnoreNullValue(true)); + exist.setLastUpdateTime(new DateTime()); + return userDao.update(exist, id) > 0; + } + + /** + * 获取单个用户 + * + * @param id 主键id + * @return 单个用户对象 + */ + @Override + public User getUser(Long id) { + return userDao.findOneById(id); + } + + /** + * 获取用户列表 + * + * @param user 用户实体 + * @return 用户列表 + */ + @Override + public List getUser(User user) { + return userDao.findByExample(user); + } +} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/resources/application.yml b/demo-orm-jdbctemplate/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-jdbctemplate/src/main/resources/application.yml rename to demo-orm-jdbctemplate/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/resources/db/data.sql b/demo-orm-jdbctemplate/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-jdbctemplate/src/main/resources/db/data.sql rename to demo-orm-jdbctemplate/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/resources/db/schema.sql b/demo-orm-jdbctemplate/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-jdbctemplate/src/main/resources/db/schema.sql rename to demo-orm-jdbctemplate/src/main/resources/db/schema.sql diff --git a/spring-boot-demo-orm-jdbctemplate/src/test/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplicationTests.java b/demo-orm-jdbctemplate/src/test/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplicationTests.java similarity index 100% rename from spring-boot-demo-orm-jdbctemplate/src/test/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplicationTests.java rename to demo-orm-jdbctemplate/src/test/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplicationTests.java diff --git a/spring-boot-demo-orm-jpa/.gitignore b/demo-orm-jpa/.gitignore similarity index 100% rename from spring-boot-demo-orm-jpa/.gitignore rename to demo-orm-jpa/.gitignore diff --git a/demo-orm-jpa/README.md b/demo-orm-jpa/README.md new file mode 100644 index 0000000..416a1b8 --- /dev/null +++ b/demo-orm-jpa/README.md @@ -0,0 +1,550 @@ +# spring-boot-demo-orm-jpa +> 此 demo 主要演示了 Spring Boot 如何使用 JPA 操作数据库,包含简单使用以及级联使用。 + +## 主要代码 + +### pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-jpa + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-jpa + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-orm-jpa + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +### JpaConfig.java +```java +/** + *

    + * JPA配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 11:05 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaAuditing +@EnableJpaRepositories(basePackages = "com.xkcoding.orm.jpa.repository", transactionManagerRef = "jpaTransactionManager") +public class JpaConfig { + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource dataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + HibernateJpaVendorAdapter japVendor = new HibernateJpaVendorAdapter(); + japVendor.setGenerateDdl(false); + LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactory.setDataSource(dataSource()); + entityManagerFactory.setJpaVendorAdapter(japVendor); + entityManagerFactory.setPackagesToScan("com.xkcoding.orm.jpa.entity"); + return entityManagerFactory; + } + + @Bean + public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } +} +``` +### User.java +```java +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:06 + */ +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +@Data +@Builder +@Entity +@Table(name = "orm_user") +@ToString(callSuper = true) +public class User extends AbstractAuditModel { + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + @Column(name = "phone_number") + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 上次登录时间 + */ + @Column(name = "last_login_time") + private Date lastLoginTime; + + /** + * 关联部门表 + * 1、关系维护端,负责多对多关系的绑定和解除 + * 2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User) + * 3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Department) + * 4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名, + * 即表名为user_department + * 关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id,这里使用referencedColumnName指定 + * 关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,department_id + * 主表就是关系维护端对应的表,从表就是关系被维护端对应的表 + */ + @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "orm_user_dept", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "dept_id", referencedColumnName = "id")) + private Collection departmentList; + +} +``` +### Department.java +```java +/** + *

    + * 部门实体类 + *

    + * + * @author 76peter + * @date Created in 2019-10-01 18:07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "orm_department") +@ToString(callSuper = true) +public class Department extends AbstractAuditModel { + + /** + * 部门名 + */ + @Column(name = "name", columnDefinition = "varchar(255) not null") + private String name; + + /** + * 上级部门id + */ + @ManyToOne(cascade = {CascadeType.REFRESH}, optional = true) + @JoinColumn(name = "superior", referencedColumnName = "id") + private Department superior; + /** + * 所属层级 + */ + @Column(name = "levels", columnDefinition = "int not null default 0") + private Integer levels; + /** + * 排序 + */ + @Column(name = "order_no", columnDefinition = "int not null default 0") + private Integer orderNo; + /** + * 子部门集合 + */ + @OneToMany(cascade = {CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER, mappedBy = "superior") + private Collection children; + + /** + * 部门下用户集合 + */ + @ManyToMany(mappedBy = "departmentList") + private Collection userList; + +} +``` +### AbstractAuditModel.java +```java +/** + *

    + * 实体通用父类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:01 + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Data +public abstract class AbstractAuditModel implements Serializable { + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "create_time", nullable = false, updatable = false) + @CreatedDate + private Date createTime; + + /** + * 上次更新时间 + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "last_update_time", nullable = false) + @LastModifiedDate + private Date lastUpdateTime; +} +``` +### UserDao.java +```java +/** + *

    + * User Dao + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:07 + */ +@Repository +public interface UserDao extends JpaRepository { + +} +``` +### DepartmentDao.java +```java +/** + *

    + * User Dao + *

    + * + * @author 76peter + * @date Created in 2019-10-01 18:07 + */ +@Repository +public interface DepartmentDao extends JpaRepository { + /** + * 根据层级查询部门 + * + * @param level 层级 + * @return 部门列表 + */ + List findDepartmentsByLevels(Integer level); +} +``` +### application.yml +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + datasource: + jdbc-url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + initialization-mode: always + continue-on-error: true + schema: + - "classpath:db/schema.sql" + data: + - "classpath:db/data.sql" + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SpringBootDemoHikariCP + max-lifetime: 60000 + connection-timeout: 30000 + jpa: + show-sql: true + hibernate: + ddl-auto: validate + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57InnoDBDialect + open-in-view: true +logging: + level: + com.xkcoding: debug + org.hibernate.SQL: debug + org.hibernate.type: trace +``` +### UserDaoTest.java +```java +/** + *

    + * jpa 测试类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:09 + */ +@Slf4j +public class UserDaoTest extends SpringBootDemoOrmJpaApplicationTests { + @Autowired + private UserDao userDao; + + /** + * 测试保存 + */ + @Test + public void testSave() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); + userDao.save(testSave3); + + Assert.assertNotNull(testSave3.getId()); + Optional byId = userDao.findById(testSave3.getId()); + Assert.assertTrue(byId.isPresent()); + log.debug("【byId】= {}", byId.get()); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + long count = userDao.count(); + userDao.deleteById(1L); + long left = userDao.count(); + Assert.assertEquals(count - 1, left); + } + + /** + * 测试修改 + */ + @Test + public void testUpdate() { + userDao.findById(1L).ifPresent(user -> { + user.setName("JPA修改名字"); + userDao.save(user); + }); + Assert.assertEquals("JPA修改名字", userDao.findById(1L).get().getName()); + } + + /** + * 测试查询单个 + */ + @Test + public void testQueryOne() { + Optional byId = userDao.findById(1L); + Assert.assertTrue(byId.isPresent()); + log.debug("【byId】= {}", byId.get()); + } + + /** + * 测试查询所有 + */ + @Test + public void testQueryAll() { + List users = userDao.findAll(); + Assert.assertNotEquals(0, users.size()); + log.debug("【users】= {}", users); + } + + /** + * 测试分页排序查询 + */ + @Test + public void testQueryPage() { + // 初始化数据 + initData(); + // JPA分页的时候起始页是页码减1 + Integer currentPage = 0; + Integer pageSize = 5; + Sort sort = Sort.by(Sort.Direction.DESC, "id"); + PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort); + Page userPage = userDao.findAll(pageRequest); + + Assert.assertEquals(5, userPage.getSize()); + Assert.assertEquals(userDao.count(), userPage.getTotalElements()); + log.debug("【id】= {}", userPage.getContent().stream().map(User::getId).collect(Collectors.toList())); + } + + /** + * 初始化10条数据 + */ + private void initData() { + List userList = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + String salt = IdUtil.fastSimpleUUID(); + int index = 3 + i; + User user = User.builder().name("testSave" + index).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + index + "@xkcoding.com").phoneNumber("1730000000" + index).status(1).lastLoginTime(new DateTime()).build(); + userList.add(user); + } + userDao.saveAll(userList); + } + +} +``` +### DepartmentDaoTest.java +```java +/** + *

    + * jpa 测试类 + *

    + * + * @author 76peter + * @date Created in 2018-11-07 14:09 + */ +@Slf4j +public class DepartmentDaoTest extends SpringBootDemoOrmJpaApplicationTests { + @Autowired + private DepartmentDao departmentDao; + @Autowired + private UserDao userDao; + + /** + * 测试保存 ,根节点 + */ + @Test + @Transactional + public void testSave() { + Collection departmentList = departmentDao.findDepartmentsByLevels(0); + + if (departmentList.size() == 0) { + Department testSave1 = Department.builder().name("testSave1").orderNo(0).levels(0).superior(null).build(); + Department testSave1_1 = Department.builder().name("testSave1_1").orderNo(0).levels(1).superior(testSave1).build(); + Department testSave1_2 = Department.builder().name("testSave1_2").orderNo(0).levels(1).superior(testSave1).build(); + Department testSave1_1_1 = Department.builder().name("testSave1_1_1").orderNo(0).levels(2).superior(testSave1_1).build(); + departmentList.add(testSave1); + departmentList.add(testSave1_1); + departmentList.add(testSave1_2); + departmentList.add(testSave1_1_1); + departmentDao.saveAll(departmentList); + + Collection deptall = departmentDao.findAll(); + log.debug("【部门】= {}", JSONArray.toJSONString((List) deptall)); + } + + + userDao.findById(1L).ifPresent(user -> { + user.setName("添加部门"); + Department dept = departmentDao.findById(2L).get(); + user.setDepartmentList(departmentList); + userDao.save(user); + }); + + log.debug("用户部门={}", JSONUtil.toJsonStr(userDao.findById(1L).get().getDepartmentList())); + + + departmentDao.findById(2L).ifPresent(dept -> { + Collection userlist = dept.getUserList(); + //关联关系由user维护中间表,department userlist不会发生变化,可以增加查询方法来处理 重写getUserList方法 + log.debug("部门下用户={}", JSONUtil.toJsonStr(userlist)); + }); + + + userDao.findById(1L).ifPresent(user -> { + user.setName("清空部门"); + user.setDepartmentList(null); + userDao.save(user); + }); + log.debug("用户部门={}", userDao.findById(1L).get().getDepartmentList()); + + } +} +``` + +### 其余代码及 SQL 参见本 demo + +## 参考 + +- Spring Data JPA 官方文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/ diff --git a/demo-orm-jpa/pom.xml b/demo-orm-jpa/pom.xml new file mode 100644 index 0000000..f9b7249 --- /dev/null +++ b/demo-orm-jpa/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + demo-orm-jpa + 1.0.0-SNAPSHOT + jar + + demo-orm-jpa + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-orm-jpa + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java new file mode 100644 index 0000000..7592f6b --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.orm.jpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-28 22:58 + */ +@SpringBootApplication +public class SpringBootDemoOrmJpaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmJpaApplication.class, args); + } +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java new file mode 100644 index 0000000..495301d --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java @@ -0,0 +1,54 @@ +package com.xkcoding.orm.jpa.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +/** + *

    + * JPA配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 11:05 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaAuditing +@EnableJpaRepositories(basePackages = "com.xkcoding.orm.jpa.repository", transactionManagerRef = "jpaTransactionManager") +public class JpaConfig { + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource dataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + HibernateJpaVendorAdapter japVendor = new HibernateJpaVendorAdapter(); + japVendor.setGenerateDdl(false); + LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactory.setDataSource(dataSource()); + entityManagerFactory.setJpaVendorAdapter(japVendor); + entityManagerFactory.setPackagesToScan("com.xkcoding.orm.jpa.entity"); + return entityManagerFactory; + } + + @Bean + public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java new file mode 100644 index 0000000..0de0bb6 --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java @@ -0,0 +1,61 @@ +package com.xkcoding.orm.jpa.entity; + +import com.xkcoding.orm.jpa.entity.base.AbstractAuditModel; +import lombok.*; + +import javax.persistence.*; +import java.util.Collection; + +/** + *

    + * 部门实体类 + *

    + * + * @author 76peter + * @date Created in 2019-10-01 18:07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "orm_department") +@ToString(callSuper = true) +public class Department extends AbstractAuditModel { + + /** + * 部门名 + */ + @Column(name = "name", columnDefinition = "varchar(255) not null") + private String name; + + /** + * 上级部门id + */ + @ManyToOne(cascade = {CascadeType.REFRESH}, optional = true) + @JoinColumn(name = "superior", referencedColumnName = "id") + private Department superior; + /** + * 所属层级 + */ + @Column(name = "levels", columnDefinition = "int not null default 0") + private Integer levels; + /** + * 排序 + */ + @Column(name = "order_no", columnDefinition = "int not null default 0") + private Integer orderNo; + /** + * 子部门集合 + */ + @OneToMany(cascade = {CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER, mappedBy = "superior") + private Collection children; + + /** + * 部门下用户集合 + */ + @ManyToMany(mappedBy = "departmentList") + private Collection userList; + +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java new file mode 100644 index 0000000..0ea9407 --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java @@ -0,0 +1,79 @@ +package com.xkcoding.orm.jpa.entity; + +import com.xkcoding.orm.jpa.entity.base.AbstractAuditModel; +import lombok.*; + +import javax.persistence.*; +import java.util.Collection; +import java.util.Date; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:06 + */ +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +@Data +@Builder +@Entity +@Table(name = "orm_user") +@ToString(callSuper = true) +public class User extends AbstractAuditModel { + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + @Column(name = "phone_number") + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 上次登录时间 + */ + @Column(name = "last_login_time") + private Date lastLoginTime; + + /** + * 关联部门表 + * 1、关系维护端,负责多对多关系的绑定和解除 + * 2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User) + * 3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Department) + * 4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名, + * 即表名为user_department + * 关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id,这里使用referencedColumnName指定 + * 关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,department_id + * 主表就是关系维护端对应的表,从表就是关系被维护端对应的表 + */ + @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "orm_user_dept", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "dept_id", referencedColumnName = "id")) + private Collection departmentList; + +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java new file mode 100644 index 0000000..dd2f2e4 --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java @@ -0,0 +1,46 @@ +package com.xkcoding.orm.jpa.entity.base; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + *

    + * 实体通用父类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:01 + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Data +public abstract class AbstractAuditModel implements Serializable { + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "create_time", nullable = false, updatable = false) + @CreatedDate + private Date createTime; + + /** + * 上次更新时间 + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "last_update_time", nullable = false) + @LastModifiedDate + private Date lastUpdateTime; +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java new file mode 100644 index 0000000..4767bde --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java @@ -0,0 +1,27 @@ +package com.xkcoding.orm.jpa.repository; + +import com.xkcoding.orm.jpa.entity.Department; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +/** + *

    + * User Dao + *

    + * + * @author 76peter + * @date Created in 2019-10-01 18:07 + */ +@Repository +public interface DepartmentDao extends JpaRepository { + /** + * 根据层级查询部门 + * + * @param level 层级 + * @return 部门列表 + */ + List findDepartmentsByLevels(Integer level); +} diff --git a/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java new file mode 100644 index 0000000..c728793 --- /dev/null +++ b/demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java @@ -0,0 +1,18 @@ +package com.xkcoding.orm.jpa.repository; + +import com.xkcoding.orm.jpa.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + *

    + * User Dao + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:07 + */ +@Repository +public interface UserDao extends JpaRepository { + +} diff --git a/spring-boot-demo-orm-jpa/src/main/resources/application.yml b/demo-orm-jpa/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-jpa/src/main/resources/application.yml rename to demo-orm-jpa/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-jpa/src/main/resources/db/data.sql b/demo-orm-jpa/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-jpa/src/main/resources/db/data.sql rename to demo-orm-jpa/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-jpa/src/main/resources/db/schema.sql b/demo-orm-jpa/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-jpa/src/main/resources/db/schema.sql rename to demo-orm-jpa/src/main/resources/db/schema.sql diff --git a/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplicationTests.java b/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplicationTests.java similarity index 100% rename from spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplicationTests.java rename to demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplicationTests.java diff --git a/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java b/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java new file mode 100644 index 0000000..876d07d --- /dev/null +++ b/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java @@ -0,0 +1,81 @@ +package com.xkcoding.orm.jpa.repository; + +import cn.hutool.json.JSONUtil; +import com.xkcoding.orm.jpa.SpringBootDemoOrmJpaApplicationTests; +import com.xkcoding.orm.jpa.entity.Department; +import com.xkcoding.orm.jpa.entity.User; +import lombok.extern.slf4j.Slf4j; +import net.minidev.json.JSONArray; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.transaction.Transactional; +import java.util.Collection; +import java.util.List; + +/** + *

    + * jpa 测试类 + *

    + * + * @author 76peter + * @date Created in 2018-11-07 14:09 + */ +@Slf4j +public class DepartmentDaoTest extends SpringBootDemoOrmJpaApplicationTests { + @Autowired + private DepartmentDao departmentDao; + @Autowired + private UserDao userDao; + + /** + * 测试保存 ,根节点 + */ + @Test + @Transactional + public void testSave() { + Collection departmentList = departmentDao.findDepartmentsByLevels(0); + + if (departmentList.size() == 0) { + Department testSave1 = Department.builder().name("testSave1").orderNo(0).levels(0).superior(null).build(); + Department testSave1_1 = Department.builder().name("testSave1_1").orderNo(0).levels(1).superior(testSave1).build(); + Department testSave1_2 = Department.builder().name("testSave1_2").orderNo(0).levels(1).superior(testSave1).build(); + Department testSave1_1_1 = Department.builder().name("testSave1_1_1").orderNo(0).levels(2).superior(testSave1_1).build(); + departmentList.add(testSave1); + departmentList.add(testSave1_1); + departmentList.add(testSave1_2); + departmentList.add(testSave1_1_1); + departmentDao.saveAll(departmentList); + + Collection deptall = departmentDao.findAll(); + log.debug("【部门】= {}", JSONArray.toJSONString((List) deptall)); + } + + + userDao.findById(1L).ifPresent(user -> { + user.setName("添加部门"); + Department dept = departmentDao.findById(2L).get(); + user.setDepartmentList(departmentList); + userDao.save(user); + }); + + log.debug("用户部门={}", JSONUtil.toJsonStr(userDao.findById(1L).get().getDepartmentList())); + + + departmentDao.findById(2L).ifPresent(dept -> { + Collection userlist = dept.getUserList(); + //关联关系由user维护中间表,department userlist不会发生变化,可以增加查询方法来处理 重写getUserList方法 + log.debug("部门下用户={}", JSONUtil.toJsonStr(userlist)); + }); + + + userDao.findById(1L).ifPresent(user -> { + user.setName("清空部门"); + user.setDepartmentList(null); + userDao.save(user); + }); + log.debug("用户部门={}", userDao.findById(1L).get().getDepartmentList()); + + } + +} diff --git a/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java b/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java new file mode 100644 index 0000000..20dde46 --- /dev/null +++ b/demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java @@ -0,0 +1,125 @@ +package com.xkcoding.orm.jpa.repository; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.SecureUtil; +import com.xkcoding.orm.jpa.SpringBootDemoOrmJpaApplicationTests; +import com.xkcoding.orm.jpa.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + *

    + * jpa 测试类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-07 14:09 + */ +@Slf4j +public class UserDaoTest extends SpringBootDemoOrmJpaApplicationTests { + @Autowired + private UserDao userDao; + + /** + * 测试保存 + */ + @Test + public void testSave() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); + userDao.save(testSave3); + + Assert.assertNotNull(testSave3.getId()); + Optional byId = userDao.findById(testSave3.getId()); + Assert.assertTrue(byId.isPresent()); + log.debug("【byId】= {}", byId.get()); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + long count = userDao.count(); + userDao.deleteById(1L); + long left = userDao.count(); + Assert.assertEquals(count - 1, left); + } + + /** + * 测试修改 + */ + @Test + public void testUpdate() { + userDao.findById(1L).ifPresent(user -> { + user.setName("JPA修改名字"); + userDao.save(user); + }); + Assert.assertEquals("JPA修改名字", userDao.findById(1L).get().getName()); + } + + /** + * 测试查询单个 + */ + @Test + public void testQueryOne() { + Optional byId = userDao.findById(1L); + Assert.assertTrue(byId.isPresent()); + log.debug("【byId】= {}", byId.get()); + } + + /** + * 测试查询所有 + */ + @Test + public void testQueryAll() { + List users = userDao.findAll(); + Assert.assertNotEquals(0, users.size()); + log.debug("【users】= {}", users); + } + + /** + * 测试分页排序查询 + */ + @Test + public void testQueryPage() { + // 初始化数据 + initData(); + // JPA分页的时候起始页是页码减1 + Integer currentPage = 0; + Integer pageSize = 5; + Sort sort = Sort.by(Sort.Direction.DESC, "id"); + PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort); + Page userPage = userDao.findAll(pageRequest); + + Assert.assertEquals(5, userPage.getSize()); + Assert.assertEquals(userDao.count(), userPage.getTotalElements()); + log.debug("【id】= {}", userPage.getContent().stream().map(User::getId).collect(Collectors.toList())); + } + + /** + * 初始化10条数据 + */ + private void initData() { + List userList = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + String salt = IdUtil.fastSimpleUUID(); + int index = 3 + i; + User user = User.builder().name("testSave" + index).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + index + "@xkcoding.com").phoneNumber("1730000000" + index).status(1).lastLoginTime(new DateTime()).build(); + userList.add(user); + } + userDao.saveAll(userList); + } + +} diff --git a/spring-boot-demo-orm-mybatis-mapper-page/.gitignore b/demo-orm-mybatis-mapper-page/.gitignore similarity index 100% rename from spring-boot-demo-orm-mybatis-mapper-page/.gitignore rename to demo-orm-mybatis-mapper-page/.gitignore diff --git a/demo-orm-mybatis-mapper-page/README.md b/demo-orm-mybatis-mapper-page/README.md new file mode 100644 index 0000000..94d7725 --- /dev/null +++ b/demo-orm-mybatis-mapper-page/README.md @@ -0,0 +1,331 @@ +# spring-boot-demo-orm-mybatis-mapper-page + +> 此 demo 演示了 Spring Boot 如何集成通用Mapper插件和分页助手插件,简化Mybatis开发,带给你难以置信的开发体验。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-mybatis-mapper-page + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-mybatis-mapper-page + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.0.4 + 1.2.9 + + + + + org.springframework.boot + spring-boot-starter + + + + + tk.mybatis + mapper-spring-boot-starter + ${mybatis.mapper.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${mybatis.pagehelper.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + mysql + mysql-connector-java + + + + + spring-boot-demo-orm-mybatis-mapper-page + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoOrmMybatisApplication.java + +```java +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 13:43 + */ +@SpringBootApplication +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.MapperAndPage.mapper"}) // 注意:这里的 MapperScan 是 tk.mybatis.spring.annotation.MapperScan 这个包下的 +public class SpringBootDemoOrmMybatisMapperPageApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmMybatisMapperPageApplication.class, args); + } +} +``` + +## application.yml + +```yaml +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + initialization-mode: always + continue-on-error: true + schema: + - "classpath:db/schema.sql" + data: + - "classpath:db/data.sql" + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SpringBootDemoHikariCP + max-lifetime: 60000 + connection-timeout: 30000 +logging: + level: + com.xkcoding: debug + com.xkcoding.orm.mybatis.MapperAndPage.mapper: trace +mybatis: + configuration: + # 下划线转驼峰 + map-underscore-to-camel-case: true + mapper-locations: classpath:mappers/*.xml + type-aliases-package: com.xkcoding.orm.mybatis.MapperAndPage.entity +mapper: + mappers: + - tk.mybatis.mapper.common.Mapper + not-empty: true + style: camelhump + wrap-keyword: "`{0}`" + safe-delete: true + safe-update: true + identity: MYSQL +pagehelper: + auto-dialect: true + helper-dialect: mysql + reasonable: true + params: count=countSql +``` + +## UserMapper.java + +```java +/** + *

    + * UserMapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 14:15 + */ +@Component +// 注意:这里的Mapper是tk.mybatis.mapper.common.Mapper包下的 +public interface UserMapper extends Mapper, MySqlMapper { +} +``` + +## UserMapperTest.java + +```java +/** + *

    + * UserMapper 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 14:25 + */ +@Slf4j +public class UserMapperTest extends SpringBootDemoOrmMybatisMapperPageApplicationTests { + + @Autowired + private UserMapper userMapper; + + /** + * 测试通用Mapper - 保存 + */ + @Test + public void testInsert() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + userMapper.insertUseGeneratedKeys(testSave3); + Assert.assertNotNull(testSave3.getId()); + log.debug("【测试主键回写#testSave3.getId()】= {}", testSave3.getId()); + } + + /** + * 测试通用Mapper - 批量保存 + */ + @Test + public void testInsertList() { + List userList = Lists.newArrayList(); + for (int i = 4; i < 14; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + userList.add(user); + } + int i = userMapper.insertList(userList); + Assert.assertEquals(userList.size(), i); + List ids = userList.stream().map(User::getId).collect(Collectors.toList()); + log.debug("【测试主键回写#userList.ids】= {}", ids); + } + + /** + * 测试通用Mapper - 删除 + */ + @Test + public void testDelete() { + Long primaryKey = 1L; + int i = userMapper.deleteByPrimaryKey(primaryKey); + Assert.assertEquals(1, i); + User user = userMapper.selectByPrimaryKey(primaryKey); + Assert.assertNull(user); + } + + /** + * 测试通用Mapper - 更新 + */ + @Test + public void testUpdate() { + Long primaryKey = 1L; + User user = userMapper.selectByPrimaryKey(primaryKey); + user.setName("通用Mapper名字更新"); + int i = userMapper.updateByPrimaryKeySelective(user); + Assert.assertEquals(1, i); + User update = userMapper.selectByPrimaryKey(primaryKey); + Assert.assertNotNull(update); + Assert.assertEquals("通用Mapper名字更新", update.getName()); + log.debug("【update】= {}", update); + } + + /** + * 测试通用Mapper - 查询单个 + */ + @Test + public void testQueryOne(){ + User user = userMapper.selectByPrimaryKey(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + /** + * 测试通用Mapper - 查询全部 + */ + @Test + public void testQueryAll() { + List users = userMapper.selectAll(); + Assert.assertTrue(CollUtil.isNotEmpty(users)); + log.debug("【users】= {}", users); + } + + /** + * 测试分页助手 - 分页排序查询 + */ + @Test + public void testQueryByPageAndSort() { + initData(); + int currentPage = 1; + int pageSize = 5; + String orderBy = "id desc"; + int count = userMapper.selectCount(null); + PageHelper.startPage(currentPage, pageSize, orderBy); + List users = userMapper.selectAll(); + PageInfo userPageInfo = new PageInfo<>(users); + Assert.assertEquals(5, userPageInfo.getSize()); + Assert.assertEquals(count, userPageInfo.getTotal()); + log.debug("【userPageInfo】= {}", userPageInfo); + } + + /** + * 测试通用Mapper - 条件查询 + */ + @Test + public void testQueryByCondition() { + initData(); + Example example = new Example(User.class); + // 过滤 + example.createCriteria().andLike("name", "%Save1%").orEqualTo("phoneNumber", "17300000001"); + // 排序 + example.setOrderByClause("id desc"); + int count = userMapper.selectCountByExample(example); + // 分页 + PageHelper.startPage(1, 3); + // 查询 + List userList = userMapper.selectByExample(example); + PageInfo userPageInfo = new PageInfo<>(userList); + Assert.assertEquals(3, userPageInfo.getSize()); + Assert.assertEquals(count, userPageInfo.getTotal()); + log.debug("【userPageInfo】= {}", userPageInfo); + } + + /** + * 初始化数据 + */ + private void initData() { + testInsertList(); + } + +} +``` + +## 参考 + +- 通用Mapper官方文档:https://github.com/abel533/Mapper/wiki/1.integration +- pagehelper 官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md diff --git a/demo-orm-mybatis-mapper-page/pom.xml b/demo-orm-mybatis-mapper-page/pom.xml new file mode 100644 index 0000000..0bfc5ed --- /dev/null +++ b/demo-orm-mybatis-mapper-page/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + demo-orm-mybatis-mapper-page + 1.0.0-SNAPSHOT + jar + + demo-orm-mybatis-mapper-page + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.0.4 + 1.2.9 + + + + + org.springframework.boot + spring-boot-starter + + + + + tk.mybatis + mapper-spring-boot-starter + ${mybatis.mapper.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${mybatis.pagehelper.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + mysql + mysql-connector-java + + + + + demo-orm-mybatis-mapper-page + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java new file mode 100644 index 0000000..69a7ade --- /dev/null +++ b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.orm.mybatis.MapperAndPage; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import tk.mybatis.spring.annotation.MapperScan; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 13:43 + */ +@SpringBootApplication +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.MapperAndPage.mapper"}) +public class SpringBootDemoOrmMybatisMapperPageApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmMybatisMapperPageApplication.class, args); + } +} diff --git a/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java new file mode 100644 index 0000000..3106bfb --- /dev/null +++ b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java @@ -0,0 +1,81 @@ +package com.xkcoding.orm.mybatis.MapperAndPage.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import tk.mybatis.mapper.annotation.KeySql; + +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.Date; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 14:14 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "orm_user") +public class User implements Serializable { + private static final long serialVersionUID = -1840831686851699943L; + + /** + * 主键 + */ + @Id + @KeySql(useGeneratedKeys = true) + private Long id; + + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 上次登录时间 + */ + private Date lastLoginTime; + + /** + * 上次更新时间 + */ + private Date lastUpdateTime; +} diff --git a/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java new file mode 100644 index 0000000..bd8d121 --- /dev/null +++ b/demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package com.xkcoding.orm.mybatis.MapperAndPage.mapper; + +import com.xkcoding.orm.mybatis.MapperAndPage.entity.User; +import org.springframework.stereotype.Component; +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.MySqlMapper; + +/** + *

    + * UserMapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 14:15 + */ +@Component +public interface UserMapper extends Mapper, MySqlMapper { +} diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/application.yml b/demo-orm-mybatis-mapper-page/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/application.yml rename to demo-orm-mybatis-mapper-page/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/db/data.sql b/demo-orm-mybatis-mapper-page/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/db/data.sql rename to demo-orm-mybatis-mapper-page/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/db/schema.sql b/demo-orm-mybatis-mapper-page/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-mybatis-mapper-page/src/main/resources/db/schema.sql rename to demo-orm-mybatis-mapper-page/src/main/resources/db/schema.sql diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplicationTests.java b/demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplicationTests.java similarity index 100% rename from spring-boot-demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplicationTests.java rename to demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplicationTests.java diff --git a/demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java b/demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java new file mode 100644 index 0000000..4167636 --- /dev/null +++ b/demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java @@ -0,0 +1,159 @@ +package com.xkcoding.orm.mybatis.MapperAndPage.mapper; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.SecureUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.xkcoding.orm.mybatis.MapperAndPage.SpringBootDemoOrmMybatisMapperPageApplicationTests; +import com.xkcoding.orm.mybatis.MapperAndPage.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import tk.mybatis.mapper.entity.Example; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

    + * UserMapper 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 14:25 + */ +@Slf4j +public class UserMapperTest extends SpringBootDemoOrmMybatisMapperPageApplicationTests { + + @Autowired + private UserMapper userMapper; + + /** + * 测试通用Mapper - 保存 + */ + @Test + public void testInsert() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + userMapper.insertUseGeneratedKeys(testSave3); + Assert.assertNotNull(testSave3.getId()); + log.debug("【测试主键回写#testSave3.getId()】= {}", testSave3.getId()); + } + + /** + * 测试通用Mapper - 批量保存 + */ + @Test + public void testInsertList() { + List userList = Lists.newArrayList(); + for (int i = 4; i < 14; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + userList.add(user); + } + int i = userMapper.insertList(userList); + Assert.assertEquals(userList.size(), i); + List ids = userList.stream().map(User::getId).collect(Collectors.toList()); + log.debug("【测试主键回写#userList.ids】= {}", ids); + } + + /** + * 测试通用Mapper - 删除 + */ + @Test + public void testDelete() { + Long primaryKey = 1L; + int i = userMapper.deleteByPrimaryKey(primaryKey); + Assert.assertEquals(1, i); + User user = userMapper.selectByPrimaryKey(primaryKey); + Assert.assertNull(user); + } + + /** + * 测试通用Mapper - 更新 + */ + @Test + public void testUpdate() { + Long primaryKey = 1L; + User user = userMapper.selectByPrimaryKey(primaryKey); + user.setName("通用Mapper名字更新"); + int i = userMapper.updateByPrimaryKeySelective(user); + Assert.assertEquals(1, i); + User update = userMapper.selectByPrimaryKey(primaryKey); + Assert.assertNotNull(update); + Assert.assertEquals("通用Mapper名字更新", update.getName()); + log.debug("【update】= {}", update); + } + + /** + * 测试通用Mapper - 查询单个 + */ + @Test + public void testQueryOne() { + User user = userMapper.selectByPrimaryKey(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + /** + * 测试通用Mapper - 查询全部 + */ + @Test + public void testQueryAll() { + List users = userMapper.selectAll(); + Assert.assertTrue(CollUtil.isNotEmpty(users)); + log.debug("【users】= {}", users); + } + + /** + * 测试分页助手 - 分页排序查询 + */ + @Test + public void testQueryByPageAndSort() { + initData(); + int currentPage = 1; + int pageSize = 5; + String orderBy = "id desc"; + int count = userMapper.selectCount(null); + PageHelper.startPage(currentPage, pageSize, orderBy); + List users = userMapper.selectAll(); + PageInfo userPageInfo = new PageInfo<>(users); + Assert.assertEquals(5, userPageInfo.getSize()); + Assert.assertEquals(count, userPageInfo.getTotal()); + log.debug("【userPageInfo】= {}", userPageInfo); + } + + /** + * 测试通用Mapper - 条件查询 + */ + @Test + public void testQueryByCondition() { + initData(); + Example example = new Example(User.class); + // 过滤 + example.createCriteria().andLike("name", "%Save1%").orEqualTo("phoneNumber", "17300000001"); + // 排序 + example.setOrderByClause("id desc"); + int count = userMapper.selectCountByExample(example); + // 分页 + PageHelper.startPage(1, 3); + // 查询 + List userList = userMapper.selectByExample(example); + PageInfo userPageInfo = new PageInfo<>(userList); + Assert.assertEquals(3, userPageInfo.getSize()); + Assert.assertEquals(count, userPageInfo.getTotal()); + log.debug("【userPageInfo】= {}", userPageInfo); + } + + /** + * 初始化数据 + */ + private void initData() { + testInsertList(); + } + +} diff --git a/spring-boot-demo-orm-mybatis-plus/.gitignore b/demo-orm-mybatis-plus/.gitignore similarity index 100% rename from spring-boot-demo-orm-mybatis-plus/.gitignore rename to demo-orm-mybatis-plus/.gitignore diff --git a/demo-orm-mybatis-plus/README.md b/demo-orm-mybatis-plus/README.md new file mode 100644 index 0000000..2806054 --- /dev/null +++ b/demo-orm-mybatis-plus/README.md @@ -0,0 +1,513 @@ +# spring-boot-demo-orm-mybatis-plus + +> 此 demo 演示了 Spring Boot 如何集成 mybatis-plus,简化Mybatis开发,带给你难以置信的开发体验。 +> +> - 2019-09-14 新增:ActiveRecord 模式操作 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-mybatis-plus + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-mybatis-plus + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.0.5 + + + + + org.springframework.boot + spring-boot-starter + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis.plus.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-orm-mybatis-plus + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## MybatisPlusConfig.java + +```java +/** + *

    + * mybatis-plus 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 17:29 + */ +@Configuration +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.plus.mapper"}) +@EnableTransactionManagement +public class MybatisPlusConfig { + /** + * 性能分析拦截器,不建议生产使用 + */ + @Bean + public PerformanceInterceptor performanceInterceptor(){ + return new PerformanceInterceptor(); + } + + /** + * 分页插件 + */ + @Bean + public PaginationInterceptor paginationInterceptor() { + return new PaginationInterceptor(); + } +} +``` + +## CommonFieldHandler.java + +```java +package com.xkcoding.orm.mybatis.plus.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + *

    + * 通用字段填充 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 17:40 + */ +@Slf4j +@Component +public class CommonFieldHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + log.info("start insert fill ...."); + this.setFieldValByName("createTime", new Date(), metaObject); + this.setFieldValByName("lastUpdateTime", new Date(), metaObject); + } + + @Override + public void updateFill(MetaObject metaObject) { + log.info("start update fill ...."); + this.setFieldValByName("lastUpdateTime", new Date(), metaObject); + } +} +``` + +## application.yml + +```yaml +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + initialization-mode: always + continue-on-error: true + schema: + - "classpath:db/schema.sql" + data: + - "classpath:db/data.sql" + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SpringBootDemoHikariCP + max-lifetime: 60000 + connection-timeout: 30000 +logging: + level: + com.xkcoding: debug + com.xkcoding.orm.mybatis.plus.mapper: trace +mybatis-plus: + mapper-locations: classpath:mappers/*.xml + #实体扫描,多个package用逗号或者分号分隔 + typeAliasesPackage: com.xkcoding.orm.mybatis.plus.entity + global-config: + # 数据库相关配置 + db-config: + #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; + id-type: auto + #字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断" + field-strategy: not_empty + #驼峰下划线转换 + table-underline: true + #是否开启大写命名,默认不开启 + #capital-mode: true + #逻辑删除配置 + #logic-delete-value: 1 + #logic-not-delete-value: 0 + db-type: mysql + #刷新mapper 调试神器 + refresh: true + # 原生配置 + configuration: + map-underscore-to-camel-case: true + cache-enabled: true +``` + +## UserMapper.java + +```java +/** + *

    + * UserMapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 16:57 + */ +@Component +public interface UserMapper extends BaseMapper { +} +``` + +## UserService.java + +```java +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:10 + */ +public interface UserService extends IService { +} +``` + +## UserServiceImpl.java + +```java +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:10 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { +} +``` + +## UserServiceTest.java + +```java +/** + *

    + * User Service 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:13 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoOrmMybatisPlusApplicationTests { + @Autowired + private UserService userService; + + /** + * 测试Mybatis-Plus 新增 + */ + @Test + public void testSave() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); + boolean save = userService.save(testSave3); + Assert.assertTrue(save); + log.debug("【测试id回显#testSave3.getId()】= {}", testSave3.getId()); + } + + /** + * 测试Mybatis-Plus 批量新增 + */ + @Test + public void testSaveList() { + List userList = Lists.newArrayList(); + for (int i = 4; i < 14; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).build(); + userList.add(user); + } + boolean batch = userService.saveBatch(userList); + Assert.assertTrue(batch); + List ids = userList.stream().map(User::getId).collect(Collectors.toList()); + log.debug("【userList#ids】= {}", ids); + } + + /** + * 测试Mybatis-Plus 删除 + */ + @Test + public void testDelete() { + boolean remove = userService.removeById(1L); + Assert.assertTrue(remove); + User byId = userService.getById(1L); + Assert.assertNull(byId); + } + + /** + * 测试Mybatis-Plus 修改 + */ + @Test + public void testUpdate() { + User user = userService.getById(1L); + Assert.assertNotNull(user); + user.setName("MybatisPlus修改名字"); + boolean b = userService.updateById(user); + Assert.assertTrue(b); + User update = userService.getById(1L); + Assert.assertEquals("MybatisPlus修改名字", update.getName()); + log.debug("【update】= {}", update); + } + + /** + * 测试Mybatis-Plus 查询单个 + */ + @Test + public void testQueryOne() { + User user = userService.getById(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + /** + * 测试Mybatis-Plus 查询全部 + */ + @Test + public void testQueryAll() { + List list = userService.list(new QueryWrapper<>()); + Assert.assertTrue(CollUtil.isNotEmpty(list)); + log.debug("【list】= {}", list); + } + + /** + * 测试Mybatis-Plus 分页排序查询 + */ + @Test + public void testQueryByPageAndSort() { + initData(); + int count = userService.count(new QueryWrapper<>()); + Page userPage = new Page<>(1, 5); + userPage.setDesc("id"); + IPage page = userService.page(userPage, new QueryWrapper<>()); + Assert.assertEquals(5, page.getSize()); + Assert.assertEquals(count, page.getTotal()); + log.debug("【page.getRecords()】= {}", page.getRecords()); + } + + /** + * 测试Mybatis-Plus 自定义查询 + */ + @Test + public void testQueryByCondition() { + initData(); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.like("name", "Save1").or().eq("phone_number", "17300000001").orderByDesc("id"); + int count = userService.count(wrapper); + Page userPage = new Page<>(1, 3); + IPage page = userService.page(userPage, wrapper); + Assert.assertEquals(3, page.getSize()); + Assert.assertEquals(count, page.getTotal()); + log.debug("【page.getRecords()】= {}", page.getRecords()); + } + + /** + * 初始化数据 + */ + private void initData() { + testSaveList(); + } + +} +``` + +## 2019-09-14新增 + +### ActiveRecord 模式 + +- Role.java + +```java +/** + *

    + * 角色实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-16 14:04 + */ +@Data +@TableName("orm_role") +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class Role extends Model { + /** + * 主键 + */ + private Long id; + + /** + * 角色名 + */ + private String name; + + /** + * 主键值,ActiveRecord 模式这个必须有,否则 xxById 的方法都将失效! + * 即使使用 ActiveRecord 不会用到 RoleMapper,RoleMapper 这个接口也必须创建 + */ + @Override + protected Serializable pkVal() { + + return this.id; + } +} +``` + +- RoleMapper.java + +```java +/** + *

    + * RoleMapper + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-16 14:06 + */ +public interface RoleMapper extends BaseMapper { +} +``` + +- ActiveRecordTest.java + +```java +/** + *

    + * Role + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-16 14:19 + */ +@Slf4j +public class ActiveRecordTest extends SpringBootDemoOrmMybatisPlusApplicationTests { + /** + * 测试 ActiveRecord 插入数据 + */ + @Test + public void testActiveRecordInsert() { + Role role = new Role(); + role.setName("VIP"); + Assert.assertTrue(role.insert()); + // 成功直接拿会写的 ID + log.debug("【role】= {}", role); + } + + /** + * 测试 ActiveRecord 更新数据 + */ + @Test + public void testActiveRecordUpdate() { + Assert.assertTrue(new Role().setId(1L).setName("管理员-1").updateById()); + Assert.assertTrue(new Role().update(new UpdateWrapper().lambda().set(Role::getName, "普通用户-1").eq(Role::getId, 2))); + } + + /** + * 测试 ActiveRecord 查询数据 + */ + @Test + public void testActiveRecordSelect() { + Assert.assertEquals("管理员", new Role().setId(1L).selectById().getName()); + Role role = new Role().selectOne(new QueryWrapper().lambda().eq(Role::getId, 2)); + Assert.assertEquals("普通用户", role.getName()); + List roles = new Role().selectAll(); + Assert.assertTrue(roles.size() > 0); + log.debug("【roles】= {}", roles); + } + + /** + * 测试 ActiveRecord 删除数据 + */ + @Test + public void testActiveRecordDelete() { + Assert.assertTrue(new Role().setId(1L).deleteById()); + Assert.assertTrue(new Role().delete(new QueryWrapper().lambda().eq(Role::getName, "普通用户"))); + } +} +``` + +## 参考 + +- mybatis-plus官方文档:http://mp.baomidou.com/ + diff --git a/demo-orm-mybatis-plus/pom.xml b/demo-orm-mybatis-plus/pom.xml new file mode 100644 index 0000000..9c37d04 --- /dev/null +++ b/demo-orm-mybatis-plus/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + demo-orm-mybatis-plus + 1.0.0-SNAPSHOT + jar + + demo-orm-mybatis-plus + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.1.0 + + + + + org.springframework.boot + spring-boot-starter + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis.plus.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-orm-mybatis-plus + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java new file mode 100644 index 0000000..5919a42 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.orm.mybatis.plus; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 16:48 + */ +@SpringBootApplication +public class SpringBootDemoOrmMybatisPlusApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmMybatisPlusApplication.class, args); + } +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java new file mode 100644 index 0000000..59991b2 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java @@ -0,0 +1,34 @@ +package com.xkcoding.orm.mybatis.plus.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + *

    + * 通用字段填充 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 17:40 + */ +@Slf4j +@Component +public class CommonFieldHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + log.info("start insert fill ...."); + this.setFieldValByName("createTime", new Date(), metaObject); + this.setFieldValByName("lastUpdateTime", new Date(), metaObject); + } + + @Override + public void updateFill(MetaObject metaObject) { + log.info("start update fill ...."); + this.setFieldValByName("lastUpdateTime", new Date(), metaObject); + } +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java new file mode 100644 index 0000000..6c04aa7 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java @@ -0,0 +1,37 @@ +package com.xkcoding.orm.mybatis.plus.config; + +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + *

    + * mybatis-plus 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 17:29 + */ +@Configuration +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.plus.mapper"}) +@EnableTransactionManagement +public class MybatisPlusConfig { + /** + * 性能分析拦截器,不建议生产使用 + */ + @Bean + public PerformanceInterceptor performanceInterceptor() { + return new PerformanceInterceptor(); + } + + /** + * 分页插件 + */ + @Bean + public PaginationInterceptor paginationInterceptor() { + return new PaginationInterceptor(); + } +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java new file mode 100644 index 0000000..8eee1fc --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java @@ -0,0 +1,43 @@ +package com.xkcoding.orm.mybatis.plus.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + *

    + * 角色实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-14 14:04 + */ +@Data +@TableName("orm_role") +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class Role extends Model { + /** + * 主键 + */ + private Long id; + + /** + * 角色名 + */ + private String name; + + /** + * 主键值,ActiveRecord 模式这个必须有,否则 xxById 的方法都将失效! + * 即使使用 ActiveRecord 不会用到 RoleMapper,RoleMapper 这个接口也必须创建 + */ + @Override + protected Serializable pkVal() { + + return this.id; + } +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java new file mode 100644 index 0000000..6ce14a4 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java @@ -0,0 +1,83 @@ +package com.xkcoding.orm.mybatis.plus.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +import static com.baomidou.mybatisplus.annotation.FieldFill.INSERT; +import static com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 16:49 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@TableName("orm_user") +public class User implements Serializable { + private static final long serialVersionUID = -1840831686851699943L; + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + @TableField(fill = INSERT) + private Date createTime; + + /** + * 上次登录时间 + */ + private Date lastLoginTime; + + /** + * 上次更新时间 + */ + @TableField(fill = INSERT_UPDATE) + private Date lastUpdateTime; +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java new file mode 100644 index 0000000..c595309 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java @@ -0,0 +1,15 @@ +package com.xkcoding.orm.mybatis.plus.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.orm.mybatis.plus.entity.Role; + +/** + *

    + * RoleMapper + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-14 14:06 + */ +public interface RoleMapper extends BaseMapper { +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java new file mode 100644 index 0000000..028507d --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java @@ -0,0 +1,17 @@ +package com.xkcoding.orm.mybatis.plus.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.orm.mybatis.plus.entity.User; +import org.springframework.stereotype.Component; + +/** + *

    + * UserMapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 16:57 + */ +@Component +public interface UserMapper extends BaseMapper { +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java new file mode 100644 index 0000000..8215a83 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java @@ -0,0 +1,15 @@ +package com.xkcoding.orm.mybatis.plus.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.xkcoding.orm.mybatis.plus.entity.User; + +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:10 + */ +public interface UserService extends IService { +} diff --git a/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..535d955 --- /dev/null +++ b/demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java @@ -0,0 +1,19 @@ +package com.xkcoding.orm.mybatis.plus.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xkcoding.orm.mybatis.plus.entity.User; +import com.xkcoding.orm.mybatis.plus.mapper.UserMapper; +import com.xkcoding.orm.mybatis.plus.service.UserService; +import org.springframework.stereotype.Service; + +/** + *

    + * User Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:10 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { +} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/resources/application.yml b/demo-orm-mybatis-plus/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-mybatis-plus/src/main/resources/application.yml rename to demo-orm-mybatis-plus/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/resources/db/data.sql b/demo-orm-mybatis-plus/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-mybatis-plus/src/main/resources/db/data.sql rename to demo-orm-mybatis-plus/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/resources/db/schema.sql b/demo-orm-mybatis-plus/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-mybatis-plus/src/main/resources/db/schema.sql rename to demo-orm-mybatis-plus/src/main/resources/db/schema.sql diff --git a/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java new file mode 100644 index 0000000..7a62319 --- /dev/null +++ b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.orm.mybatis.plus; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoOrmMybatisPlusApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java new file mode 100644 index 0000000..1bfc981 --- /dev/null +++ b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java @@ -0,0 +1,65 @@ +package com.xkcoding.orm.mybatis.plus.activerecord; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.xkcoding.orm.mybatis.plus.SpringBootDemoOrmMybatisPlusApplicationTests; +import com.xkcoding.orm.mybatis.plus.entity.Role; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + *

    + * Role + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-14 14:19 + */ +@Slf4j +public class ActiveRecordTest extends SpringBootDemoOrmMybatisPlusApplicationTests { + /** + * 测试 ActiveRecord 插入数据 + */ + @Test + public void testActiveRecordInsert() { + Role role = new Role(); + role.setName("VIP"); + Assert.assertTrue(role.insert()); + // 成功直接拿会写的 ID + log.debug("【role】= {}", role); + } + + /** + * 测试 ActiveRecord 更新数据 + */ + @Test + public void testActiveRecordUpdate() { + Assert.assertTrue(new Role().setId(1L).setName("管理员-1").updateById()); + Assert.assertTrue(new Role().update(new UpdateWrapper().lambda().set(Role::getName, "普通用户-1").eq(Role::getId, 2))); + } + + /** + * 测试 ActiveRecord 查询数据 + */ + @Test + public void testActiveRecordSelect() { + Assert.assertEquals("管理员", new Role().setId(1L).selectById().getName()); + Role role = new Role().selectOne(new QueryWrapper().lambda().eq(Role::getId, 2)); + Assert.assertEquals("普通用户", role.getName()); + List roles = new Role().selectAll(); + Assert.assertTrue(roles.size() > 0); + log.debug("【roles】= {}", roles); + } + + /** + * 测试 ActiveRecord 删除数据 + */ + @Test + public void testActiveRecordDelete() { + Assert.assertTrue(new Role().setId(1L).deleteById()); + Assert.assertTrue(new Role().delete(new QueryWrapper().lambda().eq(Role::getName, "普通用户"))); + } +} diff --git a/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java new file mode 100644 index 0000000..6ea2d37 --- /dev/null +++ b/demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java @@ -0,0 +1,147 @@ +package com.xkcoding.orm.mybatis.plus.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.SecureUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.xkcoding.orm.mybatis.plus.SpringBootDemoOrmMybatisPlusApplicationTests; +import com.xkcoding.orm.mybatis.plus.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

    + * User Service 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 18:13 + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoOrmMybatisPlusApplicationTests { + @Autowired + private UserService userService; + + /** + * 测试Mybatis-Plus 新增 + */ + @Test + public void testSave() { + String salt = IdUtil.fastSimpleUUID(); + User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); + boolean save = userService.save(testSave3); + Assert.assertTrue(save); + log.debug("【测试id回显#testSave3.getId()】= {}", testSave3.getId()); + } + + /** + * 测试Mybatis-Plus 批量新增 + */ + @Test + public void testSaveList() { + List userList = Lists.newArrayList(); + for (int i = 4; i < 14; i++) { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).build(); + userList.add(user); + } + boolean batch = userService.saveBatch(userList); + Assert.assertTrue(batch); + List ids = userList.stream().map(User::getId).collect(Collectors.toList()); + log.debug("【userList#ids】= {}", ids); + } + + /** + * 测试Mybatis-Plus 删除 + */ + @Test + public void testDelete() { + boolean remove = userService.removeById(1L); + Assert.assertTrue(remove); + User byId = userService.getById(1L); + Assert.assertNull(byId); + } + + /** + * 测试Mybatis-Plus 修改 + */ + @Test + public void testUpdate() { + User user = userService.getById(1L); + Assert.assertNotNull(user); + user.setName("MybatisPlus修改名字"); + boolean b = userService.updateById(user); + Assert.assertTrue(b); + User update = userService.getById(1L); + Assert.assertEquals("MybatisPlus修改名字", update.getName()); + log.debug("【update】= {}", update); + } + + /** + * 测试Mybatis-Plus 查询单个 + */ + @Test + public void testQueryOne() { + User user = userService.getById(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + /** + * 测试Mybatis-Plus 查询全部 + */ + @Test + public void testQueryAll() { + List list = userService.list(new QueryWrapper<>()); + Assert.assertTrue(CollUtil.isNotEmpty(list)); + log.debug("【list】= {}", list); + } + + /** + * 测试Mybatis-Plus 分页排序查询 + */ + @Test + public void testQueryByPageAndSort() { + initData(); + int count = userService.count(new QueryWrapper<>()); + Page userPage = new Page<>(1, 5); + userPage.setDesc("id"); + IPage page = userService.page(userPage, new QueryWrapper<>()); + Assert.assertEquals(5, page.getSize()); + Assert.assertEquals(count, page.getTotal()); + log.debug("【page.getRecords()】= {}", page.getRecords()); + } + + /** + * 测试Mybatis-Plus 自定义查询 + */ + @Test + public void testQueryByCondition() { + initData(); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.like("name", "Save1").or().eq("phone_number", "17300000001").orderByDesc("id"); + int count = userService.count(wrapper); + Page userPage = new Page<>(1, 3); + IPage page = userService.page(userPage, wrapper); + Assert.assertEquals(3, page.getSize()); + Assert.assertEquals(count, page.getTotal()); + log.debug("【page.getRecords()】= {}", page.getRecords()); + } + + /** + * 初始化数据 + */ + private void initData() { + testSaveList(); + } + +} diff --git a/spring-boot-demo-orm-mybatis/.gitignore b/demo-orm-mybatis/.gitignore similarity index 100% rename from spring-boot-demo-orm-mybatis/.gitignore rename to demo-orm-mybatis/.gitignore diff --git a/demo-orm-mybatis/README.md b/demo-orm-mybatis/README.md new file mode 100644 index 0000000..e8c16eb --- /dev/null +++ b/demo-orm-mybatis/README.md @@ -0,0 +1,278 @@ +# spring-boot-demo-orm-mybatis + +> 此 demo 演示了 Spring Boot 如何与原生的 mybatis 整合,使用了 mybatis 官方提供的脚手架 `mybatis-spring-boot-starter `可以很容易的和 Spring Boot 整合。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-orm-mybatis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-orm-mybatis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.3.2 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.version} + + + + mysql + mysql-connector-java + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-orm-mybatis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoOrmMybatisApplication.java + +```java +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 10:52 + */ +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.mapper"}) +@SpringBootApplication +public class SpringBootDemoOrmMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmMybatisApplication.class, args); + } +} +``` + +## application.yml + +```yaml +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + initialization-mode: always + continue-on-error: true + schema: + - "classpath:db/schema.sql" + data: + - "classpath:db/data.sql" + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SpringBootDemoHikariCP + max-lifetime: 60000 + connection-timeout: 30000 +logging: + level: + com.xkcoding: debug + com.xkcoding.orm.mybatis.mapper: trace +mybatis: + configuration: + # 下划线转驼峰 + map-underscore-to-camel-case: true + mapper-locations: classpath:mappers/*.xml + type-aliases-package: com.xkcoding.orm.mybatis.entity +``` + +## UserMapper.java + +```java +/** + *

    + * User Mapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 10:54 + */ +@Mapper +@Component +public interface UserMapper { + + /** + * 查询所有用户 + * + * @return 用户列表 + */ + @Select("SELECT * FROM orm_user") + List selectAllUser(); + + /** + * 根据id查询用户 + * + * @param id 主键id + * @return 当前id的用户,不存在则是 {@code null} + */ + @Select("SELECT * FROM orm_user WHERE id = #{id}") + User selectUserById(@Param("id") Long id); + + /** + * 保存用户 + * + * @param user 用户 + * @return 成功 - {@code 1} 失败 - {@code 0} + */ + int saveUser(@Param("user") User user); + + /** + * 删除用户 + * + * @param id 主键id + * @return 成功 - {@code 1} 失败 - {@code 0} + */ + int deleteById(@Param("id") Long id); + +} +``` + +## UserMapper.xml + +```xml + + + + + + INSERT INTO `orm_user` (`name`, + `password`, + `salt`, + `email`, + `phone_number`, + `status`, + `create_time`, + `last_login_time`, + `last_update_time`) + VALUES (#{user.name}, + #{user.password}, + #{user.salt}, + #{user.email}, + #{user.phoneNumber}, + #{user.status}, + #{user.createTime}, + #{user.lastLoginTime}, + #{user.lastUpdateTime}) + + + + DELETE + FROM `orm_user` + WHERE `id` = #{id} + + +``` + +## UserMapperTest.java + +```java +/** + *

    + * UserMapper 测试类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 11:25 + */ +@Slf4j +public class UserMapperTest extends SpringBootDemoOrmMybatisApplicationTests { + @Autowired + private UserMapper userMapper; + + @Test + public void selectAllUser() { + List userList = userMapper.selectAllUser(); + Assert.assertTrue(CollUtil.isNotEmpty(userList)); + log.debug("【userList】= {}", userList); + } + + @Test + public void selectUserById() { + User user = userMapper.selectUserById(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + @Test + public void saveUser() { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + int i = userMapper.saveUser(user); + Assert.assertEquals(1, i); + } + + @Test + public void deleteById() { + int i = userMapper.deleteById(1L); + Assert.assertEquals(1, i); + } +} +``` + +## 参考 + +- Mybatis官方文档:http://www.mybatis.org/mybatis-3/zh/index.html + +- Mybatis官方脚手架文档:http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ + +- Mybatis整合Spring Boot官方demo:https://github.com/mybatis/spring-boot-starter/tree/master/mybatis-spring-boot-samples diff --git a/demo-orm-mybatis/pom.xml b/demo-orm-mybatis/pom.xml new file mode 100644 index 0000000..57cf5e5 --- /dev/null +++ b/demo-orm-mybatis/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + demo-orm-mybatis + 1.0.0-SNAPSHOT + jar + + demo-orm-mybatis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.3.2 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.version} + + + + mysql + mysql-connector-java + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-orm-mybatis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java new file mode 100644 index 0000000..dc62406 --- /dev/null +++ b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.orm.mybatis; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 10:52 + */ +@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.mapper"}) +@SpringBootApplication +public class SpringBootDemoOrmMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOrmMybatisApplication.class, args); + } +} diff --git a/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java new file mode 100644 index 0000000..d361be0 --- /dev/null +++ b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java @@ -0,0 +1,75 @@ +package com.xkcoding.orm.mybatis.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + *

    + * 用户实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 10:58 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements Serializable { + private static final long serialVersionUID = -1840831686851699943L; + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String name; + + /** + * 加密后的密码 + */ + private String password; + + /** + * 加密使用的盐 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phoneNumber; + + /** + * 状态,-1:逻辑删除,0:禁用,1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 上次登录时间 + */ + private Date lastLoginTime; + + /** + * 上次更新时间 + */ + private Date lastUpdateTime; +} diff --git a/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java new file mode 100644 index 0000000..796fdfd --- /dev/null +++ b/demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java @@ -0,0 +1,56 @@ +package com.xkcoding.orm.mybatis.mapper; + +import com.xkcoding.orm.mybatis.entity.User; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + *

    + * User Mapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 10:54 + */ +@Mapper +@Component +public interface UserMapper { + + /** + * 查询所有用户 + * + * @return 用户列表 + */ + @Select("SELECT * FROM orm_user") + List selectAllUser(); + + /** + * 根据id查询用户 + * + * @param id 主键id + * @return 当前id的用户,不存在则是 {@code null} + */ + @Select("SELECT * FROM orm_user WHERE id = #{id}") + User selectUserById(@Param("id") Long id); + + /** + * 保存用户 + * + * @param user 用户 + * @return 成功 - {@code 1} 失败 - {@code 0} + */ + int saveUser(@Param("user") User user); + + /** + * 删除用户 + * + * @param id 主键id + * @return 成功 - {@code 1} 失败 - {@code 0} + */ + int deleteById(@Param("id") Long id); + +} diff --git a/spring-boot-demo-orm-mybatis/src/main/resources/application.yml b/demo-orm-mybatis/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-orm-mybatis/src/main/resources/application.yml rename to demo-orm-mybatis/src/main/resources/application.yml diff --git a/spring-boot-demo-orm-mybatis/src/main/resources/db/data.sql b/demo-orm-mybatis/src/main/resources/db/data.sql similarity index 100% rename from spring-boot-demo-orm-mybatis/src/main/resources/db/data.sql rename to demo-orm-mybatis/src/main/resources/db/data.sql diff --git a/spring-boot-demo-orm-mybatis/src/main/resources/db/schema.sql b/demo-orm-mybatis/src/main/resources/db/schema.sql similarity index 100% rename from spring-boot-demo-orm-mybatis/src/main/resources/db/schema.sql rename to demo-orm-mybatis/src/main/resources/db/schema.sql diff --git a/spring-boot-demo-orm-mybatis/src/main/resources/mappers/UserMapper.xml b/demo-orm-mybatis/src/main/resources/mappers/UserMapper.xml similarity index 100% rename from spring-boot-demo-orm-mybatis/src/main/resources/mappers/UserMapper.xml rename to demo-orm-mybatis/src/main/resources/mappers/UserMapper.xml diff --git a/spring-boot-demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplicationTests.java b/demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplicationTests.java similarity index 100% rename from spring-boot-demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplicationTests.java rename to demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplicationTests.java diff --git a/demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java b/demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java new file mode 100644 index 0000000..c25000f --- /dev/null +++ b/demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java @@ -0,0 +1,68 @@ +package com.xkcoding.orm.mybatis.mapper; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.SecureUtil; +import com.xkcoding.orm.mybatis.SpringBootDemoOrmMybatisApplicationTests; +import com.xkcoding.orm.mybatis.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + *

    + * UserMapper 测试类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-08 11:25 + */ +@Slf4j +public class UserMapperTest extends SpringBootDemoOrmMybatisApplicationTests { + @Autowired + private UserMapper userMapper; + + /** + * 测试查询所有 + */ + @Test + public void selectAllUser() { + List userList = userMapper.selectAllUser(); + Assert.assertTrue(CollUtil.isNotEmpty(userList)); + log.debug("【userList】= {}", userList); + } + + /** + * 测试根据主键查询单个 + */ + @Test + public void selectUserById() { + User user = userMapper.selectUserById(1L); + Assert.assertNotNull(user); + log.debug("【user】= {}", user); + } + + /** + * 测试保存 + */ + @Test + public void saveUser() { + String salt = IdUtil.fastSimpleUUID(); + User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); + int i = userMapper.saveUser(user); + Assert.assertEquals(1, i); + } + + /** + * 测试根据主键删除 + */ + @Test + public void deleteById() { + int i = userMapper.deleteById(1L); + Assert.assertEquals(1, i); + } +} diff --git a/spring-boot-demo-properties/.gitignore b/demo-pay/.gitignore similarity index 100% rename from spring-boot-demo-properties/.gitignore rename to demo-pay/.gitignore diff --git a/spring-boot-demo-tio/src/main/resources/application.properties b/demo-pay/README.md similarity index 100% rename from spring-boot-demo-tio/src/main/resources/application.properties rename to demo-pay/README.md diff --git a/demo-pay/pom.xml b/demo-pay/pom.xml new file mode 100644 index 0000000..0c0c252 --- /dev/null +++ b/demo-pay/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + demo-pay + 1.0.0-SNAPSHOT + jar + + demo-pay + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.7.0 + 3.4.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.github.javen205 + IJPay-All + ${ijpay.version} + + + + com.google.zxing + core + ${zxing.version} + + + + com.google.zxing + javase + ${zxing.version} + + + + com.alipay.sdk + alipay-sdk-java + 4.10.159.ALL + + + + org.projectlombok + lombok + true + + + + + demo-pay + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-pay/src/main/java/com/xkcoding/pay/SpringBootDemoPayApplication.java b/demo-pay/src/main/java/com/xkcoding/pay/SpringBootDemoPayApplication.java new file mode 100644 index 0000000..a8990fe --- /dev/null +++ b/demo-pay/src/main/java/com/xkcoding/pay/SpringBootDemoPayApplication.java @@ -0,0 +1,23 @@ +package com.xkcoding.pay; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2020-10-26 11:12 + */ +@SpringBootApplication +@RestController +public class SpringBootDemoPayApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoPayApplication.class, args); + } + +} diff --git a/spring-boot-demo-swagger/src/main/resources/application.yml b/demo-pay/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-swagger/src/main/resources/application.yml rename to demo-pay/src/main/resources/application.yml diff --git a/demo-pay/src/test/java/com/xkcoding/pay/SpringBootDemoPayApplicationTests.java b/demo-pay/src/test/java/com/xkcoding/pay/SpringBootDemoPayApplicationTests.java new file mode 100644 index 0000000..6d655a6 --- /dev/null +++ b/demo-pay/src/test/java/com/xkcoding/pay/SpringBootDemoPayApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.pay; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoPayApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-rbac-security/.gitignore b/demo-properties/.gitignore similarity index 100% rename from spring-boot-demo-rbac-security/.gitignore rename to demo-properties/.gitignore diff --git a/demo-properties/README.md b/demo-properties/README.md new file mode 100644 index 0000000..f326bef --- /dev/null +++ b/demo-properties/README.md @@ -0,0 +1,191 @@ +# spring-boot-demo-properties + +> 本 demo 演示如何获取配置文件的自定义配置,以及如何多环境下的配置文件信息的获取 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-properties + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-properties + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-properties + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## ApplicationProperty.java + +```java +/** + *

    + * 项目配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:50 + */ +@Data +@Component +public class ApplicationProperty { + @Value("${application.name}") + private String name; + @Value("${application.version}") + private String version; +} +``` + +## DeveloperProperty.java + +```java +/** + *

    + * 开发人员配置信息 + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:51 + */ +@Data +@ConfigurationProperties(prefix = "developer") +@Component +public class DeveloperProperty { + private String name; + private String website; + private String qq; + private String phoneNumber; +} +``` + +## PropertyController.java + +```java +/** + *

    + * 测试Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:49 + */ +@RestController +public class PropertyController { + private final ApplicationProperty applicationProperty; + private final DeveloperProperty developerProperty; + + @Autowired + public PropertyController(ApplicationProperty applicationProperty, DeveloperProperty developerProperty) { + this.applicationProperty = applicationProperty; + this.developerProperty = developerProperty; + } + + @GetMapping("/property") + public Dict index() { + return Dict.create().set("applicationProperty", applicationProperty).set("developerProperty", developerProperty); + } +} +``` + +## additional-spring-configuration-metadata.json + +> 位置: src/main/resources/META-INF/additional-spring-configuration-metadata.json + +```json +{ + "properties": [ + { + "name": "application.name", + "description": "Default value is artifactId in pom.xml.", + "type": "java.lang.String" + }, + { + "name": "application.version", + "description": "Default value is version in pom.xml.", + "type": "java.lang.String" + }, + { + "name": "developer.name", + "description": "The Developer Name.", + "type": "java.lang.String" + }, + { + "name": "developer.website", + "description": "The Developer Website.", + "type": "java.lang.String" + }, + { + "name": "developer.qq", + "description": "The Developer QQ Number.", + "type": "java.lang.String" + }, + { + "name": "developer.phone-number", + "description": "The Developer Phone Number.", + "type": "java.lang.String" + } + ] +} +``` + diff --git a/demo-properties/pom.xml b/demo-properties/pom.xml new file mode 100644 index 0000000..75e2245 --- /dev/null +++ b/demo-properties/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + demo-properties + 1.0.0-SNAPSHOT + jar + + demo-properties + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + demo-properties + + + org.springframework.boot + spring-boot-maven-plugin + + + + + src/main/resources + true + + + + + diff --git a/demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java b/demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java new file mode 100644 index 0000000..aa5fe24 --- /dev/null +++ b/demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.properties; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:48 + */ +@SpringBootApplication +public class SpringBootDemoPropertiesApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoPropertiesApplication.class, args); + } +} diff --git a/demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java b/demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java new file mode 100644 index 0000000..099cf1c --- /dev/null +++ b/demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java @@ -0,0 +1,33 @@ +package com.xkcoding.properties.controller; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.properties.property.ApplicationProperty; +import com.xkcoding.properties.property.DeveloperProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 测试Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:49 + */ +@RestController +public class PropertyController { + private final ApplicationProperty applicationProperty; + private final DeveloperProperty developerProperty; + + @Autowired + public PropertyController(ApplicationProperty applicationProperty, DeveloperProperty developerProperty) { + this.applicationProperty = applicationProperty; + this.developerProperty = developerProperty; + } + + @GetMapping("/property") + public Dict index() { + return Dict.create().set("applicationProperty", applicationProperty).set("developerProperty", developerProperty); + } +} diff --git a/demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java b/demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java new file mode 100644 index 0000000..4c73df2 --- /dev/null +++ b/demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java @@ -0,0 +1,22 @@ +package com.xkcoding.properties.property; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + *

    + * 项目配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:50 + */ +@Data +@Component +public class ApplicationProperty { + @Value("${application.name}") + private String name; + @Value("${application.version}") + private String version; +} diff --git a/demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java b/demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java new file mode 100644 index 0000000..635319e --- /dev/null +++ b/demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java @@ -0,0 +1,23 @@ +package com.xkcoding.properties.property; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + *

    + * 开发人员配置信息 + *

    + * + * @author yangkai.shen + * @date Created in 2018-09-29 10:51 + */ +@Data +@ConfigurationProperties(prefix = "developer") +@Component +public class DeveloperProperty { + private String name; + private String website; + private String qq; + private String phoneNumber; +} diff --git a/spring-boot-demo-properties/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/demo-properties/src/main/resources/META-INF/additional-spring-configuration-metadata.json similarity index 100% rename from spring-boot-demo-properties/src/main/resources/META-INF/additional-spring-configuration-metadata.json rename to demo-properties/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/spring-boot-demo-properties/src/main/resources/application-dev.yml b/demo-properties/src/main/resources/application-dev.yml similarity index 100% rename from spring-boot-demo-properties/src/main/resources/application-dev.yml rename to demo-properties/src/main/resources/application-dev.yml diff --git a/spring-boot-demo-properties/src/main/resources/application-prod.yml b/demo-properties/src/main/resources/application-prod.yml similarity index 100% rename from spring-boot-demo-properties/src/main/resources/application-prod.yml rename to demo-properties/src/main/resources/application-prod.yml diff --git a/spring-boot-demo-properties/src/main/resources/application.yml b/demo-properties/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-properties/src/main/resources/application.yml rename to demo-properties/src/main/resources/application.yml diff --git a/demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java b/demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java new file mode 100644 index 0000000..79cdf57 --- /dev/null +++ b/demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.properties; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoPropertiesApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-ratelimit-guava/.gitignore b/demo-ratelimit-guava/.gitignore similarity index 100% rename from spring-boot-demo-ratelimit-guava/.gitignore rename to demo-ratelimit-guava/.gitignore diff --git a/demo-ratelimit-guava/README.md b/demo-ratelimit-guava/README.md new file mode 100644 index 0000000..896041f --- /dev/null +++ b/demo-ratelimit-guava/README.md @@ -0,0 +1,218 @@ +# spring-boot-demo-ratelimit-guava + +> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Guava 的 RateLimiter 实现限流,旨在保护 API 被恶意频繁访问的问题。 + +## 1. 主要代码 + +### 1.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-ratelimit-guava + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-ratelimit-guava + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-ratelimit-guava + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 1.2. 定义一个限流注解 `RateLimiter.java` + +> 注意代码里使用了 `AliasFor` 设置一组属性的别名,所以获取注解的时候,需要通过 `Spring` 提供的注解工具类 `AnnotationUtils` 获取,不可以通过 `AOP` 参数注入的方式获取,否则有些属性的值将会设置不进去。 + +```java +/** + *

    + * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:14 + * @see AnnotationUtils + *

    + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + int NOT_LIMITED = 0; + + /** + * qps + */ + @AliasFor("qps") double value() default NOT_LIMITED; + + /** + * qps + */ + @AliasFor("value") double qps() default NOT_LIMITED; + + /** + * 超时时长 + */ + int timeout() default 0; + + /** + * 超时时间单位 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} +``` + +### 1.3. 定义一个切面 `RateLimiterAspect.java` + +```java +/** + *

    + * 限流切面 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:27 + */ +@Slf4j +@Aspect +@Component +public class RateLimiterAspect { + private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>(); + + @Pointcut("@annotation(com.xkcoding.ratelimit.guava.annotation.RateLimiter)") + public void rateLimit() { + + } + + @Around("rateLimit()") + public Object pointcut(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 + RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); + if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) { + double qps = rateLimiter.qps(); + if (RATE_LIMITER_CACHE.get(method.getName()) == null) { + // 初始化 QPS + RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps)); + } + + log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate()); + // 尝试获取令牌 + if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) { + throw new RuntimeException("手速太快了,慢点儿吧~"); + } + } + return point.proceed(); + } +} +``` + +### 1.4. 定义两个API接口用于测试限流 + +```java +/** + *

    + * 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:22 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 1.0, timeout = 300) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } +} +``` + +## 2. 测试 + +- test1 接口未被限流的时候 + +image-20190912155209716 + +- test1 接口频繁刷新,触发限流的时候 + +image-20190912155229745 + +- test2 接口不做限流,可以一直刷新 + +image-20190912155146012 + +## 3. 参考 + +- [限流原理解读之guava中的RateLimiter](https://juejin.im/post/5bb48d7b5188255c865e31bc) + +- [使用Guava的RateLimiter做限流](https://my.oschina.net/hanchao/blog/1833612) + diff --git a/demo-ratelimit-guava/pom.xml b/demo-ratelimit-guava/pom.xml new file mode 100644 index 0000000..4372346 --- /dev/null +++ b/demo-ratelimit-guava/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + demo-ratelimit-guava + 1.0.0-SNAPSHOT + jar + + demo-ratelimit-guava + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-ratelimit-guava + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java new file mode 100644 index 0000000..f99ff75 --- /dev/null +++ b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.ratelimit.guava; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:06 + */ +@SpringBootApplication +public class SpringBootDemoRatelimitGuavaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoRatelimitGuavaApplication.class, args); + } + +} diff --git a/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java new file mode 100644 index 0000000..35bb0dd --- /dev/null +++ b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java @@ -0,0 +1,43 @@ +package com.xkcoding.ratelimit.guava.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + *

    + * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:14 + * @see AnnotationUtils + *

    + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + int NOT_LIMITED = 0; + + /** + * qps + */ + @AliasFor("qps") double value() default NOT_LIMITED; + + /** + * qps + */ + @AliasFor("value") double qps() default NOT_LIMITED; + + /** + * 超时时长 + */ + int timeout() default 0; + + /** + * 超时时间单位 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} diff --git a/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java new file mode 100644 index 0000000..98ccd4a --- /dev/null +++ b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java @@ -0,0 +1,57 @@ +package com.xkcoding.ratelimit.guava.aspect; + +import com.xkcoding.ratelimit.guava.annotation.RateLimiter; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + *

    + * 限流切面 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:27 + */ +@Slf4j +@Aspect +@Component +public class RateLimiterAspect { + private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>(); + + @Pointcut("@annotation(com.xkcoding.ratelimit.guava.annotation.RateLimiter)") + public void rateLimit() { + + } + + @Around("rateLimit()") + public Object pointcut(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 + RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); + if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) { + double qps = rateLimiter.qps(); + if (RATE_LIMITER_CACHE.get(method.getName()) == null) { + // 初始化 QPS + RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps)); + } + + log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate()); + // 尝试获取令牌 + if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) { + throw new RuntimeException("手速太快了,慢点儿吧~"); + } + } + return point.proceed(); + } +} diff --git a/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java new file mode 100644 index 0000000..5ccb2b3 --- /dev/null +++ b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java @@ -0,0 +1,40 @@ +package com.xkcoding.ratelimit.guava.controller; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.ratelimit.guava.annotation.RateLimiter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 14:22 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 1.0, timeout = 300) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } + + @RateLimiter(value = 2.0, timeout = 300) + @GetMapping("/test3") + public Dict test3() { + log.info("【test3】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } +} diff --git a/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..85f9bfa --- /dev/null +++ b/demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java @@ -0,0 +1,22 @@ +package com.xkcoding.ratelimit.guava.handler; + +import cn.hutool.core.lang.Dict; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + *

    + * 全局异常拦截 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-12 15:00 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(RuntimeException.class) + public Dict handler(RuntimeException ex) { + return Dict.create().set("msg", ex.getMessage()); + } +} diff --git a/spring-boot-demo-ratelimit-guava/src/main/resources/application.yml b/demo-ratelimit-guava/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-ratelimit-guava/src/main/resources/application.yml rename to demo-ratelimit-guava/src/main/resources/application.yml diff --git a/spring-boot-demo-ratelimit-guava/src/test/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplicationTests.java b/demo-ratelimit-guava/src/test/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplicationTests.java similarity index 100% rename from spring-boot-demo-ratelimit-guava/src/test/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplicationTests.java rename to demo-ratelimit-guava/src/test/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplicationTests.java diff --git a/spring-boot-demo-ratelimit-redis/.gitignore b/demo-ratelimit-redis/.gitignore similarity index 100% rename from spring-boot-demo-ratelimit-redis/.gitignore rename to demo-ratelimit-redis/.gitignore diff --git a/demo-ratelimit-redis/README.md b/demo-ratelimit-redis/README.md new file mode 100644 index 0000000..7564ac3 --- /dev/null +++ b/demo-ratelimit-redis/README.md @@ -0,0 +1,296 @@ +# spring-boot-demo-ratelimit-redis + +> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Redis + Lua 脚本实现分布式限流,旨在保护 API 被恶意频繁访问的问题,是 `spring-boot-demo-ratelimit-guava` 的升级版。 + +## 1. 主要代码 + +### 1.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-ratelimit-redis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-ratelimit-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-ratelimit-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 1.2. 限流注解 + +```java +/** + *

    + * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:31 + * @see AnnotationUtils + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + long DEFAULT_REQUEST = 10; + + /** + * max 最大请求数 + */ + @AliasFor("max") long value() default DEFAULT_REQUEST; + + /** + * max 最大请求数 + */ + @AliasFor("value") long max() default DEFAULT_REQUEST; + + /** + * 限流key + */ + String key() default ""; + + /** + * 超时时长,默认1分钟 + */ + long timeout() default 1; + + /** + * 超时时间单位,默认 分钟 + */ + TimeUnit timeUnit() default TimeUnit.MINUTES; +} +``` + +### 1.3. AOP处理限流 + +```java +/** + *

    + * 限流切面 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:30 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RateLimiterAspect { + private final static String SEPARATOR = ":"; + private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; + private final StringRedisTemplate stringRedisTemplate; + private final RedisScript limitRedisScript; + + @Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)") + public void rateLimit() { + + } + + @Around("rateLimit()") + public Object pointcut(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 + RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); + if (rateLimiter != null) { + String key = rateLimiter.key(); + // 默认用类名+方法名做限流的 key 前缀 + if (StrUtil.isBlank(key)) { + key = method.getDeclaringClass().getName()+StrUtil.DOT+method.getName(); + } + // 最终限流的 key 为 前缀 + IP地址 + // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 + key = key + SEPARATOR + IpUtil.getIpAddr(); + + long max = rateLimiter.max(); + long timeout = rateLimiter.timeout(); + TimeUnit timeUnit = rateLimiter.timeUnit(); + boolean limited = shouldLimited(key, max, timeout, timeUnit); + if (limited) { + throw new RuntimeException("手速太快了,慢点儿吧~"); + } + } + + return point.proceed(); + } + + private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { + // 最终的 key 格式为: + // limit:自定义key:IP + // limit:类名.方法名:IP + key = REDIS_LIMIT_KEY_PREFIX + key; + // 统一使用单位毫秒 + long ttl = timeUnit.toMillis(timeout); + // 当前时间毫秒数 + long now = Instant.now().toEpochMilli(); + long expired = now - ttl; + // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String + Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); + if (executeTimes != null) { + if (executeTimes == 0) { + log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); + return true; + } else { + log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); + return false; + } + } + return false; + } +} +``` + +### 1.4. lua 脚本 + +```lua +-- 下标从 1 开始 +local key = KEYS[1] +local now = tonumber(ARGV[1]) +local ttl = tonumber(ARGV[2]) +local expired = tonumber(ARGV[3]) +-- 最大访问量 +local max = tonumber(ARGV[4]) + +-- 清除过期的数据 +-- 移除指定分数区间内的所有元素,expired 即已经过期的 score +-- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expired +redis.call('zremrangebyscore', key, 0, expired) + +-- 获取 zset 中的当前元素个数 +local current = tonumber(redis.call('zcard', key)) +local next = current + 1 + +if next > max then + -- 达到限流大小 返回 0 + return 0; +else + -- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score] + redis.call("zadd", key, now, now) + -- 每次访问均重新设置 zset 的过期时间,单位毫秒 + redis.call("pexpire", key, ttl) + return next +end +``` + +### 1.5. 接口测试 + +```java +/** + *

    + * 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:30 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 5) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } + + @RateLimiter(value = 2, key = "测试自定义key") + @GetMapping("/test3") + public Dict test3() { + log.info("【test3】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } +} +``` + +### 1.6. 其余代码参见 demo + +## 2. 测试 + +- 触发限流时控制台打印 + +![image-20190930155856711](http://static.xkcoding.com/spring-boot-demo/ratelimit/redis/063812.jpg) + +- 触发限流的时候 Redis 的数据 + +![image-20190930155735300](http://static.xkcoding.com/spring-boot-demo/ratelimit/redis/063813.jpg) + +## 3. 参考 + +- [mica-plus-redis 的分布式限流实现](https://github.com/lets-mica/mica/tree/master/mica-plus-redis) +- [Java并发:分布式应用限流 Redis + Lua 实践](https://segmentfault.com/a/1190000016042927) diff --git a/demo-ratelimit-redis/pom.xml b/demo-ratelimit-redis/pom.xml new file mode 100644 index 0000000..ed4506d --- /dev/null +++ b/demo-ratelimit-redis/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + demo-ratelimit-redis + 1.0.0-SNAPSHOT + jar + + demo-ratelimit-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-ratelimit-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java new file mode 100644 index 0000000..43cb5f2 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java @@ -0,0 +1,21 @@ +package com.xkcoding.ratelimit.redis; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 09:32 + */ +@SpringBootApplication +public class SpringBootDemoRatelimitRedisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoRatelimitRedisApplication.class, args); + } + +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java new file mode 100644 index 0000000..459b0bc --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java @@ -0,0 +1,48 @@ +package com.xkcoding.ratelimit.redis.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + *

    + * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:31 + * @see AnnotationUtils + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + long DEFAULT_REQUEST = 10; + + /** + * max 最大请求数 + */ + @AliasFor("max") long value() default DEFAULT_REQUEST; + + /** + * max 最大请求数 + */ + @AliasFor("value") long max() default DEFAULT_REQUEST; + + /** + * 限流key + */ + String key() default ""; + + /** + * 超时时长,默认1分钟 + */ + long timeout() default 1; + + /** + * 超时时间单位,默认 分钟 + */ + TimeUnit timeUnit() default TimeUnit.MINUTES; +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java new file mode 100644 index 0000000..fb2aed9 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java @@ -0,0 +1,98 @@ +package com.xkcoding.ratelimit.redis.aspect; + +import cn.hutool.core.util.StrUtil; +import com.xkcoding.ratelimit.redis.annotation.RateLimiter; +import com.xkcoding.ratelimit.redis.util.IpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** + *

    + * 限流切面 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:30 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RateLimiterAspect { + private final static String SEPARATOR = ":"; + private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; + private final StringRedisTemplate stringRedisTemplate; + private final RedisScript limitRedisScript; + + @Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)") + public void rateLimit() { + + } + + @Around("rateLimit()") + public Object pointcut(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 + RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); + if (rateLimiter != null) { + String key = rateLimiter.key(); + // 默认用类名+方法名做限流的 key 前缀 + if (StrUtil.isBlank(key)) { + key = method.getDeclaringClass().getName() + StrUtil.DOT + method.getName(); + } + // 最终限流的 key 为 前缀 + IP地址 + // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 + key = key + SEPARATOR + IpUtil.getIpAddr(); + + long max = rateLimiter.max(); + long timeout = rateLimiter.timeout(); + TimeUnit timeUnit = rateLimiter.timeUnit(); + boolean limited = shouldLimited(key, max, timeout, timeUnit); + if (limited) { + throw new RuntimeException("手速太快了,慢点儿吧~"); + } + } + + return point.proceed(); + } + + private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { + // 最终的 key 格式为: + // limit:自定义key:IP + // limit:类名.方法名:IP + key = REDIS_LIMIT_KEY_PREFIX + key; + // 统一使用单位毫秒 + long ttl = timeUnit.toMillis(timeout); + // 当前时间毫秒数 + long now = Instant.now().toEpochMilli(); + long expired = now - ttl; + // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String + Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); + if (executeTimes != null) { + if (executeTimes == 0) { + log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); + return true; + } else { + log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); + return false; + } + } + return false; + } +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java new file mode 100644 index 0000000..6716388 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java @@ -0,0 +1,28 @@ +package com.xkcoding.ratelimit.redis.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.scripting.support.ResourceScriptSource; + +/** + *

    + * Redis 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 11:37 + */ +@Configuration +public class RedisConfig { + @Bean + @SuppressWarnings("unchecked") + public RedisScript limitRedisScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/redis/limit.lua"))); + redisScript.setResultType(Long.class); + return redisScript; + } +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java new file mode 100644 index 0000000..fb1aab3 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java @@ -0,0 +1,40 @@ +package com.xkcoding.ratelimit.redis.controller; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.ratelimit.redis.annotation.RateLimiter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:30 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 5) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } + + @RateLimiter(value = 2, key = "测试自定义key") + @GetMapping("/test3") + public Dict test3() { + log.info("【test3】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..f0b67f0 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package com.xkcoding.ratelimit.redis.handler; + +import cn.hutool.core.lang.Dict; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + *

    + * 全局异常拦截 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:30 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(RuntimeException.class) + public Dict handler(RuntimeException ex) { + return Dict.create().set("msg", ex.getMessage()); + } +} diff --git a/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java new file mode 100644 index 0000000..c6c2e33 --- /dev/null +++ b/demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java @@ -0,0 +1,59 @@ +package com.xkcoding.ratelimit.redis.util; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * IP 工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2019-09-30 10:38 + */ +@Slf4j +public class IpUtil { + private final static String UNKNOWN = "unknown"; + private final static int MAX_LENGTH = 15; + + /** + * 获取IP地址 + * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 + * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 + */ + public static String getIpAddr() { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String ip = null; + try { + ip = request.getHeader("x-forwarded-for"); + if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (StrUtil.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + } catch (Exception e) { + log.error("IPUtils ERROR ", e); + } + // 使用代理,则获取第一个IP地址 + if (!StrUtil.isEmpty(ip) && ip.length() > MAX_LENGTH) { + if (ip.indexOf(StrUtil.COMMA) > 0) { + ip = ip.substring(0, ip.indexOf(StrUtil.COMMA)); + } + } + return ip; + } +} diff --git a/spring-boot-demo-ratelimit-redis/src/main/resources/application.yml b/demo-ratelimit-redis/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-ratelimit-redis/src/main/resources/application.yml rename to demo-ratelimit-redis/src/main/resources/application.yml diff --git a/spring-boot-demo-ratelimit-redis/src/main/resources/scripts/redis/limit.lua b/demo-ratelimit-redis/src/main/resources/scripts/redis/limit.lua similarity index 100% rename from spring-boot-demo-ratelimit-redis/src/main/resources/scripts/redis/limit.lua rename to demo-ratelimit-redis/src/main/resources/scripts/redis/limit.lua diff --git a/spring-boot-demo-ratelimit-redis/src/test/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimiterRedisApplicationTests.java b/demo-ratelimit-redis/src/test/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimiterRedisApplicationTests.java similarity index 100% rename from spring-boot-demo-ratelimit-redis/src/test/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimiterRedisApplicationTests.java rename to demo-ratelimit-redis/src/test/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimiterRedisApplicationTests.java diff --git a/spring-boot-demo-rbac-shiro/.gitignore b/demo-rbac-security/.gitignore similarity index 100% rename from spring-boot-demo-rbac-shiro/.gitignore rename to demo-rbac-security/.gitignore diff --git a/demo-rbac-security/README.md b/demo-rbac-security/README.md new file mode 100644 index 0000000..5f46602 --- /dev/null +++ b/demo-rbac-security/README.md @@ -0,0 +1,871 @@ +# spring-boot-demo-rbac-security + +> 此 demo 主要演示了 Spring Boot 项目如何集成 Spring Security 完成权限拦截操作。本 demo 为基于**前后端分离**的后端权限管理部分,不同于其他博客里使用的模板技术,希望对大家有所帮助。 + +## 1. 主要功能 + +- [x] 基于 `RBAC` 权限模型设计,详情参考数据库表结构设计 [`security.sql`](./sql/security.sql) +- [x] 支持**动态权限管理**,详情参考 [`RbacAuthorityService.java`](./src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java) +- [x] **登录 / 登出**部分均使用自定义 Controller 实现,未使用 `Spring Security` 内部默认的实现,适用于前后端分离项目,详情参考 [`SecurityConfig.java`](./src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java) 和 [`AuthController.java`](./src/main/java/com/xkcoding/rbac/security/controller/AuthController.java) +- [x] 持久化技术使用 `spring-data-jpa` 完成 +- [x] 使用 `JWT` 实现安全验证,同时引入 `Redis` 解决 `JWT` 无法手动设置过期的弊端,并且保证同一用户在同一时间仅支持同一设备登录,不同设备登录会将,详情参考 [`JwtUtil.java`](./src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java) +- [x] 在线人数统计,详情参考 [`MonitorService.java`](./src/main/java/com/xkcoding/rbac/security/service/MonitorService.java) 和 [`RedisUtil.java`](./src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java) +- [x] 手动踢出用户,详情参考 [`MonitorService.java`](./src/main/java/com/xkcoding/rbac/security/service/MonitorService.java) +- [x] 自定义配置不需要进行拦截的请求,详情参考 [`CustomConfig.java`](./src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java) 和 [`application.yml`](./src/main/resources/application.yml) + +## 2. 运行 + +### 2.1. 环境 + +1. JDK 1.8 以上 +2. Maven 3.5 以上 +3. Mysql 5.7 以上 +4. Redis + +### 2.2. 运行方式 + +1. 新建一个名为 `spring-boot-demo` 的数据库,字符集设置为 `utf-8`,如果数据库名不是 `spring-boot-demo` 需要在 `application.yml` 中修改 `spring.datasource.url` +2. 使用 [`security.sql`](./sql/security.sql) 这个 SQL 文件,创建数据库表和初始化RBAC数据 +3. 运行 `SpringBootDemoRbacSecurityApplication` +4. 管理员账号:admin/123456 普通用户:user/123456 +5. 使用 `POST` 请求访问 `/${contextPath}/api/auth/login` 端点,输入账号密码,登陆成功之后返回token,将获得的 token 放在具体请求的 Header 里,key 固定是 `Authorization` ,value 前缀为 `Bearer 后面加空格`再加token,并加上具体请求的参数,就可以了 +6. enjoy ~​ :kissing_smiling_eyes: + +## 3. 部分关键代码 + +### 3.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-rbac-security + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-rbac-security + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 0.9.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + io.jsonwebtoken + jjwt + ${jjwt.veersion} + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-rbac-security + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 3.2. JwtUtil.java + +> JWT 工具类,主要功能:生成JWT并存入Redis、解析JWT并校验其准确性、从Request的Header中获取JWT + +```java +/** + *

    + * JWT 工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 13:42 + */ +@EnableConfigurationProperties(JwtConfig.class) +@Configuration +@Slf4j +public class JwtUtil { + @Autowired + private JwtConfig jwtConfig; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 创建JWT + * + * @param rememberMe 记住我 + * @param id 用户id + * @param subject 用户名 + * @param roles 用户角色 + * @param authorities 用户权限 + * @return JWT + */ + public String createJWT(Boolean rememberMe, Long id, String subject, List roles, Collection authorities) { + Date now = new Date(); + JwtBuilder builder = Jwts.builder() + .setId(id.toString()) + .setSubject(subject) + .setIssuedAt(now) + .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) + .claim("roles", roles) + .claim("authorities", authorities); + + // 设置过期时间 + Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); + if (ttl > 0) { + builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue())); + } + + String jwt = builder.compact(); + // 将生成的JWT保存至Redis + stringRedisTemplate.opsForValue() + .set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS); + return jwt; + } + + /** + * 创建JWT + * + * @param authentication 用户认证信息 + * @param rememberMe 记住我 + * @return JWT + */ + public String createJWT(Authentication authentication, Boolean rememberMe) { + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); + } + + /** + * 解析JWT + * + * @param jwt JWT + * @return {@link Claims} + */ + public Claims parseJWT(String jwt) { + try { + Claims claims = Jwts.parser() + .setSigningKey(jwtConfig.getKey()) + .parseClaimsJws(jwt) + .getBody(); + + String username = claims.getSubject(); + String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username; + + // 校验redis中的JWT是否存在 + Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); + if (Objects.isNull(expire) || expire <= 0) { + throw new SecurityException(Status.TOKEN_EXPIRED); + } + + // 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期 + String redisToken = stringRedisTemplate.opsForValue() + .get(redisKey); + if (!StrUtil.equals(jwt, redisToken)) { + throw new SecurityException(Status.TOKEN_OUT_OF_CTRL); + } + return claims; + } catch (ExpiredJwtException e) { + log.error("Token 已过期"); + throw new SecurityException(Status.TOKEN_EXPIRED); + } catch (UnsupportedJwtException e) { + log.error("不支持的 Token"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (MalformedJwtException e) { + log.error("Token 无效"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (SignatureException e) { + log.error("无效的 Token 签名"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (IllegalArgumentException e) { + log.error("Token 参数不存在"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } + } + + /** + * 设置JWT过期 + * + * @param request 请求 + */ + public void invalidateJWT(HttpServletRequest request) { + String jwt = getJwtFromRequest(request); + String username = getUsernameFromJWT(jwt); + // 从redis中清除JWT + stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username); + } + + /** + * 根据 jwt 获取用户名 + * + * @param jwt JWT + * @return 用户名 + */ + public String getUsernameFromJWT(String jwt) { + Claims claims = parseJWT(jwt); + return claims.getSubject(); + } + + /** + * 从 request 的 header 中获取 JWT + * + * @param request 请求 + * @return JWT + */ + public String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + +} +``` + +### 3.3. SecurityConfig.java + +> Spring Security 配置类,主要功能:配置哪些URL不需要认证,哪些需要认证 + +```java +/** + *

    + * Security 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:46 + */ +@Configuration +@EnableWebSecurity +@EnableConfigurationProperties(CustomConfig.class) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired + private CustomConfig customConfig; + + @Autowired + private AccessDeniedHandler accessDeniedHandler; + + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public BCryptPasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(customUserDetailsService) + .passwordEncoder(encoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.cors() + + // 关闭 CSRF + .and() + .csrf() + .disable() + + // 登录行为由自己实现,参考 AuthController#login + .formLogin() + .disable() + .httpBasic() + .disable() + + // 认证请求 + .authorizeRequests() + // 所有请求都需要登录访问 + .anyRequest() + .authenticated() + // RBAC 动态 url 认证 + .anyRequest() + .access("@rbacAuthorityService.hasPermission(request,authentication)") + + // 登出行为由自己实现,参考 AuthController#logout + .and() + .logout() + .disable() + + // Session 管理 + .sessionManagement() + // 因为使用了JWT,所以这里不管理Session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + // 异常处理 + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler); + + // 添加自定义 JWT 过滤器 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + } + + /** + * 放行所有不需要登录就可以访问的请求,参见 AuthController + * 也可以在 {@link #configure(HttpSecurity)} 中配置 + * {@code http.authorizeRequests().antMatchers("/api/auth/**").permitAll()} + */ + @Override + public void configure(WebSecurity web) { + WebSecurity and = web.ignoring() + .and(); + + // 忽略 GET + customConfig.getIgnores() + .getGet() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.GET, url)); + + // 忽略 POST + customConfig.getIgnores() + .getPost() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.POST, url)); + + // 忽略 DELETE + customConfig.getIgnores() + .getDelete() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.DELETE, url)); + + // 忽略 PUT + customConfig.getIgnores() + .getPut() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.PUT, url)); + + // 忽略 HEAD + customConfig.getIgnores() + .getHead() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.HEAD, url)); + + // 忽略 PATCH + customConfig.getIgnores() + .getPatch() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.PATCH, url)); + + // 忽略 OPTIONS + customConfig.getIgnores() + .getOptions() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.OPTIONS, url)); + + // 忽略 TRACE + customConfig.getIgnores() + .getTrace() + .forEach(url -> and.ignoring() + .antMatchers(HttpMethod.TRACE, url)); + + // 按照请求格式忽略 + customConfig.getIgnores() + .getPattern() + .forEach(url -> and.ignoring() + .antMatchers(url)); + + } +} +``` + +### 3.4. RbacAuthorityService.java + +> 路由动态鉴权类,主要功能: +> +> 1. 校验请求的合法性,排除404和405这两种异常请求 +> 2. 根据当前请求路径与该用户可访问的资源做匹配,通过则可以访问,否则,不允许访问 + +```java +/** + *

    + * 动态路由认证 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 17:17 + */ +@Component +public class RbacAuthorityService { + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Autowired + private RequestMappingHandlerMapping mapping; + + public boolean hasPermission(HttpServletRequest request, Authentication authentication) { + checkRequest(request); + + Object userInfo = authentication.getPrincipal(); + boolean hasPermission = false; + + if (userInfo instanceof UserDetails) { + UserPrincipal principal = (UserPrincipal) userInfo; + Long userId = principal.getId(); + + List roles = roleDao.selectByUserId(userId); + List roleIds = roles.stream() + .map(Role::getId) + .collect(Collectors.toList()); + List permissions = permissionDao.selectByRoleIdList(roleIds); + + //获取资源,前后端分离,所以过滤页面权限,只保留按钮权限 + List btnPerms = permissions.stream() + // 过滤页面权限 + .filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON)) + // 过滤 URL 为空 + .filter(permission -> StrUtil.isNotBlank(permission.getUrl())) + // 过滤 METHOD 为空 + .filter(permission -> StrUtil.isNotBlank(permission.getMethod())) + .collect(Collectors.toList()); + + for (Permission btnPerm : btnPerms) { + AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod()); + if (antPathMatcher.matches(request)) { + hasPermission = true; + break; + } + } + + return hasPermission; + } else { + return false; + } + } + + /** + * 校验请求是否存在 + * + * @param request 请求 + */ + private void checkRequest(HttpServletRequest request) { + // 获取当前 request 的方法 + String currentMethod = request.getMethod(); + Multimap urlMapping = allUrlMapping(); + + for (String uri : urlMapping.keySet()) { + // 通过 AntPathRequestMatcher 匹配 url + // 可以通过 2 种方式创建 AntPathRequestMatcher + // 1:new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配,因为这里我们把 方法不匹配 自定义抛出,所以,我们使用第2种方式创建 + // 2:new AntPathRequestMatcher(uri) 这种方式不校验请求方法,只校验请求路径 + AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri); + if (antPathMatcher.matches(request)) { + if (!urlMapping.get(uri) + .contains(currentMethod)) { + throw new SecurityException(Status.HTTP_BAD_METHOD); + } else { + return; + } + } + } + + throw new SecurityException(Status.REQUEST_NOT_FOUND); + } + + /** + * 获取 所有URL Mapping,返回格式为{"/test":["GET","POST"],"/sys":["GET","DELETE"]} + * + * @return {@link ArrayListMultimap} 格式的 URL Mapping + */ + private Multimap allUrlMapping() { + Multimap urlMapping = ArrayListMultimap.create(); + + // 获取url与类和方法的对应信息 + Map handlerMethods = mapping.getHandlerMethods(); + + handlerMethods.forEach((k, v) -> { + // 获取当前 key 下的获取所有URL + Set url = k.getPatternsCondition() + .getPatterns(); + RequestMethodsRequestCondition method = k.getMethodsCondition(); + + // 为每个URL添加所有的请求方法 + url.forEach(s -> urlMapping.putAll(s, method.getMethods() + .stream() + .map(Enum::toString) + .collect(Collectors.toList()))); + }); + + return urlMapping; + } +} +``` + +### 3.5. JwtAuthenticationFilter.java + +> JWT 认证过滤器,主要功能: +> +> 1. 过滤不需要拦截的请求 +> 2. 根据当前请求的JWT,认证用户身份信息 + +```java +/** + *

    + * Jwt 认证过滤器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:15 + */ +@Component +@Slf4j +public class JwtAuthenticationFilter extends OncePerRequestFilter { + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private CustomConfig customConfig; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + if (checkIgnores(request)) { + filterChain.doFilter(request, response); + return; + } + + String jwt = jwtUtil.getJwtFromRequest(request); + + if (StrUtil.isNotBlank(jwt)) { + try { + String username = jwtUtil.getUsernameFromJWT(jwt); + + UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext() + .setAuthentication(authentication); + filterChain.doFilter(request, response); + } catch (SecurityException e) { + ResponseUtil.renderJson(response, e); + } + } else { + ResponseUtil.renderJson(response, Status.UNAUTHORIZED, null); + } + + } + + /** + * 请求是否不需要进行权限拦截 + * + * @param request 当前请求 + * @return true - 忽略,false - 不忽略 + */ + private boolean checkIgnores(HttpServletRequest request) { + String method = request.getMethod(); + + HttpMethod httpMethod = HttpMethod.resolve(method); + if (ObjectUtil.isNull(httpMethod)) { + httpMethod = HttpMethod.GET; + } + + Set ignores = Sets.newHashSet(); + + switch (httpMethod) { + case GET: + ignores.addAll(customConfig.getIgnores() + .getGet()); + break; + case PUT: + ignores.addAll(customConfig.getIgnores() + .getPut()); + break; + case HEAD: + ignores.addAll(customConfig.getIgnores() + .getHead()); + break; + case POST: + ignores.addAll(customConfig.getIgnores() + .getPost()); + break; + case PATCH: + ignores.addAll(customConfig.getIgnores() + .getPatch()); + break; + case TRACE: + ignores.addAll(customConfig.getIgnores() + .getTrace()); + break; + case DELETE: + ignores.addAll(customConfig.getIgnores() + .getDelete()); + break; + case OPTIONS: + ignores.addAll(customConfig.getIgnores() + .getOptions()); + break; + default: + break; + } + + ignores.addAll(customConfig.getIgnores() + .getPattern()); + + if (CollUtil.isNotEmpty(ignores)) { + for (String ignore : ignores) { + AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method); + if (matcher.matches(request)) { + return true; + } + } + } + + return false; + } + +} +``` + +### 3.6. CustomUserDetailsService.java + +> 实现 `UserDetailsService` 接口,主要功能:根据用户名查询用户信息 + +```java +/** + *

    + * 自定义UserDetails查询 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 10:29 + */ +@Service +public class CustomUserDetailsService implements UserDetailsService { + @Autowired + private UserDao userDao; + + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Override + public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException { + User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone) + .orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone)); + List roles = roleDao.selectByUserId(user.getId()); + List roleIds = roles.stream() + .map(Role::getId) + .collect(Collectors.toList()); + List permissions = permissionDao.selectByRoleIdList(roleIds); + return UserPrincipal.create(user, roles, permissions); + } +} +``` + +### 3.7. RedisUtil.java + +> 主要功能:根据key的格式分页获取Redis存在的key列表 + +```java +/** + *

    + * Redis工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 20:24 + */ +@Component +@Slf4j +public class RedisUtil { + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率 + * + * @param patternKey key格式 + * @param currentPage 当前页码 + * @param pageSize 每页条数 + * @return 分页获取指定格式key + */ + public PageResult findKeysForPage(String patternKey, int currentPage, int pageSize) { + ScanOptions options = ScanOptions.scanOptions() + .match(patternKey) + .build(); + RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); + RedisConnection rc = factory.getConnection(); + Cursor cursor = rc.scan(options); + + List result = Lists.newArrayList(); + + long tmpIndex = 0; + int startIndex = (currentPage - 1) * pageSize; + int end = currentPage * pageSize; + while (cursor.hasNext()) { + String key = new String(cursor.next()); + if (tmpIndex >= startIndex && tmpIndex < end) { + result.add(key); + } + tmpIndex++; + } + + try { + cursor.close(); + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.warn("Redis连接关闭异常,", e); + } + + return new PageResult<>(result, tmpIndex); + } +} +``` + +### 3.8. MonitorService.java + +> 监控服务,主要功能:查询当前在线人数分页列表,手动踢出某个用户 + +```java +package com.xkcoding.rbac.security.service; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.PageResult; +import com.xkcoding.rbac.security.model.User; +import com.xkcoding.rbac.security.repository.UserDao; +import com.xkcoding.rbac.security.util.RedisUtil; +import com.xkcoding.rbac.security.vo.OnlineUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

    + * 监控 Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 00:55 + */ +@Service +public class MonitorService { + @Autowired + private RedisUtil redisUtil; + + @Autowired + private UserDao userDao; + + public PageResult onlineUser(Integer page, Integer size) { + PageResult keys = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, page, size); + List rows = keys.getRows(); + Long total = keys.getTotal(); + + // 根据 redis 中键获取用户名列表 + List usernameList = rows.stream() + .map(s -> StrUtil.subAfter(s, Consts.REDIS_JWT_KEY_PREFIX, true)) + .collect(Collectors.toList()); + // 根据用户名查询用户信息 + List userList = userDao.findByUsernameIn(usernameList); + + // 封装在线用户信息 + List onlineUserList = Lists.newArrayList(); + userList.forEach(user -> onlineUserList.add(OnlineUser.create(user))); + + return new PageResult<>(onlineUserList, total); + } +} +``` + +### 3.9. 其余代码参见本 demo + +## 4. 参考 + +1. Spring Security 官方文档:https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/ +2. JWT 官网:https://jwt.io/ +3. JJWT开源工具参考:https://github.com/jwtk/jjwt#quickstart +4. 授权部分参考官方文档:https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/#authorization + +4. 动态授权部分,参考博客:https://blog.csdn.net/larger5/article/details/81063438 + diff --git a/demo-rbac-security/pom.xml b/demo-rbac-security/pom.xml new file mode 100644 index 0000000..b9439a5 --- /dev/null +++ b/demo-rbac-security/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + demo-rbac-security + 1.0.0-SNAPSHOT + jar + + demo-rbac-security + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 0.9.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + io.jsonwebtoken + jjwt + ${jjwt.veersion} + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-rbac-security + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-rbac-security/sql/security.sql b/demo-rbac-security/sql/security.sql similarity index 100% rename from spring-boot-demo-rbac-security/sql/security.sql rename to demo-rbac-security/sql/security.sql diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java new file mode 100644 index 0000000..621d5e8 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.rbac.security; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:28 + */ +@SpringBootApplication +public class SpringBootDemoRbacSecurityApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoRbacSecurityApplication.class, args); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java new file mode 100644 index 0000000..59e1985 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java @@ -0,0 +1,126 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + *

    + * 通用的 API 接口封装 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 14:55 + */ +@Data +public class ApiResponse implements Serializable { + private static final long serialVersionUID = 8993485788201922830L; + + /** + * 状态码 + */ + private Integer code; + + /** + * 返回内容 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + /** + * 无参构造函数 + */ + private ApiResponse() { + + } + + /** + * 全参构造函数 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + */ + private ApiResponse(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + /** + * 构造一个自定义的API返回 + * + * @param code 状态码 + * @param message 返回内容 + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse of(Integer code, String message, Object data) { + return new ApiResponse(code, message, data); + } + + /** + * 构造一个成功且不带数据的API返回 + * + * @return ApiResponse + */ + public static ApiResponse ofSuccess() { + return ofSuccess(null); + } + + /** + * 构造一个成功且带数据的API返回 + * + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofSuccess(Object data) { + return ofStatus(Status.SUCCESS, data); + } + + /** + * 构造一个成功且自定义消息的API返回 + * + * @param message 返回内容 + * @return ApiResponse + */ + public static ApiResponse ofMessage(String message) { + return of(Status.SUCCESS.getCode(), message, null); + } + + /** + * 构造一个有状态的API返回 + * + * @param status 状态 {@link Status} + * @return ApiResponse + */ + public static ApiResponse ofStatus(Status status) { + return ofStatus(status, null); + } + + /** + * 构造一个有状态且带数据的API返回 + * + * @param status 状态 {@link IStatus} + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ofStatus(IStatus status, Object data) { + return of(status.getCode(), status.getMessage(), data); + } + + /** + * 构造一个异常的API返回 + * + * @param t 异常 + * @param {@link BaseException} 的子类 + * @return ApiResponse + */ + public static ApiResponse ofException(T t) { + return of(t.getCode(), t.getMessage(), t.getData()); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java new file mode 100644 index 0000000..43062a3 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java @@ -0,0 +1,42 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

    + * 异常基类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 14:57 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class BaseException extends RuntimeException { + private Integer code; + private String message; + private Object data; + + public BaseException(Status status) { + super(status.getMessage()); + this.code = status.getCode(); + this.message = status.getMessage(); + } + + public BaseException(Status status, Object data) { + this(status); + this.data = data; + } + + public BaseException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public BaseException(Integer code, String message, Object data) { + this(code, message); + this.data = data; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java new file mode 100644 index 0000000..754cfb2 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java @@ -0,0 +1,60 @@ +package com.xkcoding.rbac.security.common; + +/** + *

    + * 常量池 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:03 + */ +public interface Consts { + /** + * 启用 + */ + Integer ENABLE = 1; + /** + * 禁用 + */ + Integer DISABLE = 0; + + /** + * 页面 + */ + Integer PAGE = 1; + + /** + * 按钮 + */ + Integer BUTTON = 2; + + /** + * JWT 在 Redis 中保存的key前缀 + */ + String REDIS_JWT_KEY_PREFIX = "security:jwt:"; + + /** + * 星号 + */ + String SYMBOL_STAR = "*"; + + /** + * 邮箱符号 + */ + String SYMBOL_EMAIL = "@"; + + /** + * 默认当前页码 + */ + Integer DEFAULT_CURRENT_PAGE = 1; + + /** + * 默认每页条数 + */ + Integer DEFAULT_PAGE_SIZE = 10; + + /** + * 匿名用户 用户名 + */ + String ANONYMOUS_NAME = "匿名用户"; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java new file mode 100644 index 0000000..f6f58c1 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java @@ -0,0 +1,27 @@ +package com.xkcoding.rbac.security.common; + +/** + *

    + * REST API 错误码接口 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 14:35 + */ +public interface IStatus { + + /** + * 状态码 + * + * @return 状态码 + */ + Integer getCode(); + + /** + * 返回信息 + * + * @return 返回信息 + */ + String getMessage(); + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java new file mode 100644 index 0000000..e81f05f --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java @@ -0,0 +1,37 @@ +package com.xkcoding.rbac.security.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + *

    + * 通用分页参数返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 20:26 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageResult implements Serializable { + private static final long serialVersionUID = 3420391142991247367L; + + /** + * 当前页数据 + */ + private List rows; + + /** + * 总条数 + */ + private Long total; + + public static PageResult of(List rows, Long total) { + return new PageResult<>(rows, total); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java new file mode 100644 index 0000000..a9f60a7 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java @@ -0,0 +1,125 @@ +package com.xkcoding.rbac.security.common; + +import lombok.Getter; + +/** + *

    + * 通用状态码 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 14:31 + */ +@Getter +public enum Status implements IStatus { + /** + * 操作成功! + */ + SUCCESS(200, "操作成功!"), + + /** + * 操作异常! + */ + ERROR(500, "操作异常!"), + + /** + * 退出成功! + */ + LOGOUT(200, "退出成功!"), + + /** + * 请先登录! + */ + UNAUTHORIZED(401, "请先登录!"), + + /** + * 暂无权限访问! + */ + ACCESS_DENIED(403, "权限不足!"), + + /** + * 请求不存在! + */ + REQUEST_NOT_FOUND(404, "请求不存在!"), + + /** + * 请求方式不支持! + */ + HTTP_BAD_METHOD(405, "请求方式不支持!"), + + /** + * 请求异常! + */ + BAD_REQUEST(400, "请求异常!"), + + /** + * 参数不匹配! + */ + PARAM_NOT_MATCH(400, "参数不匹配!"), + + /** + * 参数不能为空! + */ + PARAM_NOT_NULL(400, "参数不能为空!"), + + /** + * 当前用户已被锁定,请联系管理员解锁! + */ + USER_DISABLED(403, "当前用户已被锁定,请联系管理员解锁!"), + + /** + * 用户名或密码错误! + */ + USERNAME_PASSWORD_ERROR(5001, "用户名或密码错误!"), + + /** + * token 已过期,请重新登录! + */ + TOKEN_EXPIRED(5002, "token 已过期,请重新登录!"), + + /** + * token 解析失败,请尝试重新登录! + */ + TOKEN_PARSE_ERROR(5002, "token 解析失败,请尝试重新登录!"), + + /** + * 当前用户已在别处登录,请尝试更改密码或重新登录! + */ + TOKEN_OUT_OF_CTRL(5003, "当前用户已在别处登录,请尝试更改密码或重新登录!"), + + /** + * 无法手动踢出自己,请尝试退出登录操作! + */ + KICKOUT_SELF(5004, "无法手动踢出自己,请尝试退出登录操作!"); + + /** + * 状态码 + */ + private Integer code; + + /** + * 返回信息 + */ + private String message; + + Status(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static Status fromCode(Integer code) { + Status[] statuses = Status.values(); + for (Status status : statuses) { + if (status.getCode().equals(code)) { + return status; + } + } + return SUCCESS; + } + + @Override + public String toString() { + return String.format(" Status:{code=%s, message=%s} ", getCode(), getMessage()); + } + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java new file mode 100644 index 0000000..bac8826 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java @@ -0,0 +1,21 @@ +package com.xkcoding.rbac.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

    + * 自定义配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-13 10:56 + */ +@ConfigurationProperties(prefix = "custom.config") +@Data +public class CustomConfig { + /** + * 不需要拦截的地址 + */ + private IgnoreConfig ignores; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java new file mode 100644 index 0000000..7723ded --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java @@ -0,0 +1,25 @@ +package com.xkcoding.rbac.security.config; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

    + * 雪花主键生成器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:28 + */ +@Configuration +public class IdConfig { + /** + * 雪花生成器 + */ + @Bean + public Snowflake snowflake() { + return IdUtil.createSnowflake(1, 1); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java new file mode 100644 index 0000000..becfacd --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java @@ -0,0 +1,62 @@ +package com.xkcoding.rbac.security.config; + +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; + +/** + *

    + * 忽略配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-17 17:37 + */ +@Data +public class IgnoreConfig { + /** + * 需要忽略的 URL 格式,不考虑请求方法 + */ + private List pattern = Lists.newArrayList(); + + /** + * 需要忽略的 GET 请求 + */ + private List get = Lists.newArrayList(); + + /** + * 需要忽略的 POST 请求 + */ + private List post = Lists.newArrayList(); + + /** + * 需要忽略的 DELETE 请求 + */ + private List delete = Lists.newArrayList(); + + /** + * 需要忽略的 PUT 请求 + */ + private List put = Lists.newArrayList(); + + /** + * 需要忽略的 HEAD 请求 + */ + private List head = Lists.newArrayList(); + + /** + * 需要忽略的 PATCH 请求 + */ + private List patch = Lists.newArrayList(); + + /** + * 需要忽略的 OPTIONS 请求 + */ + private List options = Lists.newArrayList(); + + /** + * 需要忽略的 TRACE 请求 + */ + private List trace = Lists.newArrayList(); +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..2b57bb6 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java @@ -0,0 +1,138 @@ +package com.xkcoding.rbac.security.config; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Sets; +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.exception.SecurityException; +import com.xkcoding.rbac.security.service.CustomUserDetailsService; +import com.xkcoding.rbac.security.util.JwtUtil; +import com.xkcoding.rbac.security.util.ResponseUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +/** + *

    + * Jwt 认证过滤器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:15 + */ +@Component +@Slf4j +public class JwtAuthenticationFilter extends OncePerRequestFilter { + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private CustomConfig customConfig; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + if (checkIgnores(request)) { + filterChain.doFilter(request, response); + return; + } + + String jwt = jwtUtil.getJwtFromRequest(request); + + if (StrUtil.isNotBlank(jwt)) { + try { + String username = jwtUtil.getUsernameFromJWT(jwt); + + UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } catch (SecurityException e) { + ResponseUtil.renderJson(response, e); + } + } else { + ResponseUtil.renderJson(response, Status.UNAUTHORIZED, null); + } + + } + + /** + * 请求是否不需要进行权限拦截 + * + * @param request 当前请求 + * @return true - 忽略,false - 不忽略 + */ + private boolean checkIgnores(HttpServletRequest request) { + String method = request.getMethod(); + + HttpMethod httpMethod = HttpMethod.resolve(method); + if (ObjectUtil.isNull(httpMethod)) { + httpMethod = HttpMethod.GET; + } + + Set ignores = Sets.newHashSet(); + + switch (httpMethod) { + case GET: + ignores.addAll(customConfig.getIgnores().getGet()); + break; + case PUT: + ignores.addAll(customConfig.getIgnores().getPut()); + break; + case HEAD: + ignores.addAll(customConfig.getIgnores().getHead()); + break; + case POST: + ignores.addAll(customConfig.getIgnores().getPost()); + break; + case PATCH: + ignores.addAll(customConfig.getIgnores().getPatch()); + break; + case TRACE: + ignores.addAll(customConfig.getIgnores().getTrace()); + break; + case DELETE: + ignores.addAll(customConfig.getIgnores().getDelete()); + break; + case OPTIONS: + ignores.addAll(customConfig.getIgnores().getOptions()); + break; + default: + break; + } + + ignores.addAll(customConfig.getIgnores().getPattern()); + + if (CollUtil.isNotEmpty(ignores)) { + for (String ignore : ignores) { + AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method); + if (matcher.matches(request)) { + return true; + } + } + } + + return false; + } + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java new file mode 100644 index 0000000..d75e3e3 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java @@ -0,0 +1,31 @@ +package com.xkcoding.rbac.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

    + * JWT 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 13:42 + */ +@ConfigurationProperties(prefix = "jwt.config") +@Data +public class JwtConfig { + /** + * jwt 加密 key,默认值:xkcoding. + */ + private String key = "xkcoding"; + + /** + * jwt 过期时间,默认值:600000 {@code 10 分钟}. + */ + private Long ttl = 600000L; + + /** + * 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} + */ + private Long remember = 604800000L; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java new file mode 100644 index 0000000..ef64038 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java @@ -0,0 +1,137 @@ +package com.xkcoding.rbac.security.config; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.exception.SecurityException; +import com.xkcoding.rbac.security.model.Permission; +import com.xkcoding.rbac.security.model.Role; +import com.xkcoding.rbac.security.repository.PermissionDao; +import com.xkcoding.rbac.security.repository.RoleDao; +import com.xkcoding.rbac.security.vo.UserPrincipal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

    + * 动态路由认证 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 17:17 + */ +@Component +public class RbacAuthorityService { + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Autowired + private RequestMappingHandlerMapping mapping; + + public boolean hasPermission(HttpServletRequest request, Authentication authentication) { + checkRequest(request); + + Object userInfo = authentication.getPrincipal(); + boolean hasPermission = false; + + if (userInfo instanceof UserDetails) { + UserPrincipal principal = (UserPrincipal) userInfo; + Long userId = principal.getId(); + + List roles = roleDao.selectByUserId(userId); + List roleIds = roles.stream().map(Role::getId).collect(Collectors.toList()); + List permissions = permissionDao.selectByRoleIdList(roleIds); + + //获取资源,前后端分离,所以过滤页面权限,只保留按钮权限 + List btnPerms = permissions.stream() + // 过滤页面权限 + .filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON)) + // 过滤 URL 为空 + .filter(permission -> StrUtil.isNotBlank(permission.getUrl())) + // 过滤 METHOD 为空 + .filter(permission -> StrUtil.isNotBlank(permission.getMethod())).collect(Collectors.toList()); + + for (Permission btnPerm : btnPerms) { + AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod()); + if (antPathMatcher.matches(request)) { + hasPermission = true; + break; + } + } + + return hasPermission; + } else { + return false; + } + } + + /** + * 校验请求是否存在 + * + * @param request 请求 + */ + private void checkRequest(HttpServletRequest request) { + // 获取当前 request 的方法 + String currentMethod = request.getMethod(); + Multimap urlMapping = allUrlMapping(); + + for (String uri : urlMapping.keySet()) { + // 通过 AntPathRequestMatcher 匹配 url + // 可以通过 2 种方式创建 AntPathRequestMatcher + // 1:new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配,因为这里我们把 方法不匹配 自定义抛出,所以,我们使用第2种方式创建 + // 2:new AntPathRequestMatcher(uri) 这种方式不校验请求方法,只校验请求路径 + AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri); + if (antPathMatcher.matches(request)) { + if (!urlMapping.get(uri).contains(currentMethod)) { + throw new SecurityException(Status.HTTP_BAD_METHOD); + } else { + return; + } + } + } + + throw new SecurityException(Status.REQUEST_NOT_FOUND); + } + + /** + * 获取 所有URL Mapping,返回格式为{"/test":["GET","POST"],"/sys":["GET","DELETE"]} + * + * @return {@link ArrayListMultimap} 格式的 URL Mapping + */ + private Multimap allUrlMapping() { + Multimap urlMapping = ArrayListMultimap.create(); + + // 获取url与类和方法的对应信息 + Map handlerMethods = mapping.getHandlerMethods(); + + handlerMethods.forEach((k, v) -> { + // 获取当前 key 下的获取所有URL + Set url = k.getPatternsCondition().getPatterns(); + RequestMethodsRequestCondition method = k.getMethodsCondition(); + + // 为每个URL添加所有的请求方法 + url.forEach(s -> urlMapping.putAll(s, method.getMethods().stream().map(Enum::toString).collect(Collectors.toList()))); + }); + + return urlMapping; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java new file mode 100644 index 0000000..4a0ea93 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java @@ -0,0 +1,39 @@ +package com.xkcoding.rbac.security.config; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.io.Serializable; + +/** + *

    + * redis配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 15:16 + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java new file mode 100644 index 0000000..2b2d75f --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java @@ -0,0 +1,132 @@ +package com.xkcoding.rbac.security.config; + +import com.xkcoding.rbac.security.service.CustomUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + *

    + * Security 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:46 + */ +@Configuration +@EnableWebSecurity +@EnableConfigurationProperties(CustomConfig.class) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired + private CustomConfig customConfig; + + @Autowired + private AccessDeniedHandler accessDeniedHandler; + + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public BCryptPasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(customUserDetailsService).passwordEncoder(encoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + // @formatter:off + http.cors() + // 关闭 CSRF + .and().csrf().disable() + // 登录行为由自己实现,参考 AuthController#login + .formLogin().disable() + .httpBasic().disable() + + // 认证请求 + .authorizeRequests() + // 所有请求都需要登录访问 + .anyRequest() + .authenticated() + // RBAC 动态 url 认证 + .anyRequest() + .access("@rbacAuthorityService.hasPermission(request,authentication)") + + // 登出行为由自己实现,参考 AuthController#logout + .and().logout().disable() + // Session 管理 + .sessionManagement() + // 因为使用了JWT,所以这里不管理Session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + // 异常处理 + .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler); + // @formatter:on + + // 添加自定义 JWT 过滤器 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + } + + /** + * 放行所有不需要登录就可以访问的请求,参见 AuthController + * 也可以在 {@link #configure(HttpSecurity)} 中配置 + * {@code http.authorizeRequests().antMatchers("/api/auth/**").permitAll()} + */ + @Override + public void configure(WebSecurity web) { + WebSecurity and = web.ignoring().and(); + + // 忽略 GET + customConfig.getIgnores().getGet().forEach(url -> and.ignoring().antMatchers(HttpMethod.GET, url)); + + // 忽略 POST + customConfig.getIgnores().getPost().forEach(url -> and.ignoring().antMatchers(HttpMethod.POST, url)); + + // 忽略 DELETE + customConfig.getIgnores().getDelete().forEach(url -> and.ignoring().antMatchers(HttpMethod.DELETE, url)); + + // 忽略 PUT + customConfig.getIgnores().getPut().forEach(url -> and.ignoring().antMatchers(HttpMethod.PUT, url)); + + // 忽略 HEAD + customConfig.getIgnores().getHead().forEach(url -> and.ignoring().antMatchers(HttpMethod.HEAD, url)); + + // 忽略 PATCH + customConfig.getIgnores().getPatch().forEach(url -> and.ignoring().antMatchers(HttpMethod.PATCH, url)); + + // 忽略 OPTIONS + customConfig.getIgnores().getOptions().forEach(url -> and.ignoring().antMatchers(HttpMethod.OPTIONS, url)); + + // 忽略 TRACE + customConfig.getIgnores().getTrace().forEach(url -> and.ignoring().antMatchers(HttpMethod.TRACE, url)); + + // 按照请求格式忽略 + customConfig.getIgnores().getPattern().forEach(url -> and.ignoring().antMatchers(url)); + + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java new file mode 100644 index 0000000..888f5ae --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java @@ -0,0 +1,25 @@ +package com.xkcoding.rbac.security.config; + +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.util.ResponseUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.access.AccessDeniedHandler; + +/** + *

    + * Security 结果处理配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 17:31 + */ +@Configuration +public class SecurityHandlerConfig { + + @Bean + public AccessDeniedHandler accessDeniedHandler() { + return (request, response, accessDeniedException) -> ResponseUtil.renderJson(response, Status.ACCESS_DENIED, null); + } + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java new file mode 100644 index 0000000..cadddfd --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java @@ -0,0 +1,24 @@ +package com.xkcoding.rbac.security.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + *

    + * MVC配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 16:09 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + private static final long MAX_AGE_SECS = 3600; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*").allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE").maxAge(MAX_AGE_SECS); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java new file mode 100644 index 0000000..200587f --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java @@ -0,0 +1,65 @@ +package com.xkcoding.rbac.security.controller; + +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.exception.SecurityException; +import com.xkcoding.rbac.security.payload.LoginRequest; +import com.xkcoding.rbac.security.util.JwtUtil; +import com.xkcoding.rbac.security.vo.JwtResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +/** + *

    + * 认证 Controller,包括用户注册,用户登录请求 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 17:23 + */ +@Slf4j +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 登录 + */ + @PostMapping("/login") + public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) { + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmailOrPhone(), loginRequest.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + String jwt = jwtUtil.createJWT(authentication, loginRequest.getRememberMe()); + return ApiResponse.ofSuccess(new JwtResponse(jwt)); + } + + @PostMapping("/logout") + public ApiResponse logout(HttpServletRequest request) { + try { + // 设置JWT过期 + jwtUtil.invalidateJWT(request); + } catch (SecurityException e) { + throw new SecurityException(Status.UNAUTHORIZED); + } + return ApiResponse.ofStatus(Status.LOGOUT); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java new file mode 100644 index 0000000..1176363 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java @@ -0,0 +1,62 @@ +package com.xkcoding.rbac.security.controller; + +import cn.hutool.core.collection.CollUtil; +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.PageResult; +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.exception.SecurityException; +import com.xkcoding.rbac.security.payload.PageCondition; +import com.xkcoding.rbac.security.service.MonitorService; +import com.xkcoding.rbac.security.util.PageUtil; +import com.xkcoding.rbac.security.util.SecurityUtil; +import com.xkcoding.rbac.security.vo.OnlineUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + *

    + * 监控 Controller,在线用户,手动踢出用户等功能 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 20:55 + */ +@Slf4j +@RestController +@RequestMapping("/api/monitor") +public class MonitorController { + @Autowired + private MonitorService monitorService; + + /** + * 在线用户列表 + * + * @param pageCondition 分页参数 + */ + @GetMapping("/online/user") + public ApiResponse onlineUser(PageCondition pageCondition) { + PageUtil.checkPageCondition(pageCondition, PageCondition.class); + PageResult pageResult = monitorService.onlineUser(pageCondition); + return ApiResponse.ofSuccess(pageResult); + } + + /** + * 批量踢出在线用户 + * + * @param names 用户名列表 + */ + @DeleteMapping("/online/user/kickout") + public ApiResponse kickoutOnlineUser(@RequestBody List names) { + if (CollUtil.isEmpty(names)) { + throw new SecurityException(Status.PARAM_NOT_NULL); + } + if (names.contains(SecurityUtil.getCurrentUsername())) { + throw new SecurityException(Status.KICKOUT_SELF); + } + monitorService.kickout(names); + return ApiResponse.ofSuccess(); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java new file mode 100644 index 0000000..764cfdf --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java @@ -0,0 +1,36 @@ +package com.xkcoding.rbac.security.controller; + +import com.xkcoding.rbac.security.common.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + *

    + * 测试Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:44 + */ +@Slf4j +@RestController +@RequestMapping("/test") +public class TestController { + @GetMapping + public ApiResponse list() { + log.info("测试列表查询"); + return ApiResponse.ofMessage("测试列表查询"); + } + + @PostMapping + public ApiResponse add() { + log.info("测试列表添加"); + return ApiResponse.ofMessage("测试列表添加"); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id) { + log.info("测试列表修改"); + return ApiResponse.ofSuccess("测试列表修改"); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java new file mode 100644 index 0000000..f1e7ffb --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java @@ -0,0 +1,34 @@ +package com.xkcoding.rbac.security.exception; + +import com.xkcoding.rbac.security.common.BaseException; +import com.xkcoding.rbac.security.common.Status; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

    + * 全局异常 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 17:24 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SecurityException extends BaseException { + public SecurityException(Status status) { + super(status); + } + + public SecurityException(Status status, Object data) { + super(status, data); + } + + public SecurityException(Integer code, String message) { + super(code, message); + } + + public SecurityException(Integer code, String message, Object data) { + super(code, message, data); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..1f17f07 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,69 @@ +package com.xkcoding.rbac.security.exception.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.BaseException; +import com.xkcoding.rbac.security.common.Status; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; + +import javax.validation.ConstraintViolationException; + +/** + *

    + * 全局统一异常处理 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 17:00 + */ +@ControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(value = Exception.class) + @ResponseBody + public ApiResponse handlerException(Exception e) { + if (e instanceof NoHandlerFoundException) { + log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod()); + return ApiResponse.ofStatus(Status.REQUEST_NOT_FOUND); + } else if (e instanceof HttpRequestMethodNotSupportedException) { + log.error("【全局异常拦截】HttpRequestMethodNotSupportedException: 当前请求方式 {}, 支持请求方式 {}", ((HttpRequestMethodNotSupportedException) e).getMethod(), JSONUtil.toJsonStr(((HttpRequestMethodNotSupportedException) e).getSupportedHttpMethods())); + return ApiResponse.ofStatus(Status.HTTP_BAD_METHOD); + } else if (e instanceof MethodArgumentNotValidException) { + log.error("【全局异常拦截】MethodArgumentNotValidException", e); + return ApiResponse.of(Status.BAD_REQUEST.getCode(), ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage(), null); + } else if (e instanceof ConstraintViolationException) { + log.error("【全局异常拦截】ConstraintViolationException", e); + return ApiResponse.of(Status.BAD_REQUEST.getCode(), CollUtil.getFirst(((ConstraintViolationException) e).getConstraintViolations()).getMessage(), null); + } else if (e instanceof MethodArgumentTypeMismatchException) { + log.error("【全局异常拦截】MethodArgumentTypeMismatchException: 参数名 {}, 异常信息 {}", ((MethodArgumentTypeMismatchException) e).getName(), ((MethodArgumentTypeMismatchException) e).getMessage()); + return ApiResponse.ofStatus(Status.PARAM_NOT_MATCH); + } else if (e instanceof HttpMessageNotReadableException) { + log.error("【全局异常拦截】HttpMessageNotReadableException: 错误信息 {}", ((HttpMessageNotReadableException) e).getMessage()); + return ApiResponse.ofStatus(Status.PARAM_NOT_NULL); + } else if (e instanceof BadCredentialsException) { + log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage()); + return ApiResponse.ofStatus(Status.USERNAME_PASSWORD_ERROR); + } else if (e instanceof DisabledException) { + log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage()); + return ApiResponse.ofStatus(Status.USER_DISABLED); + } else if (e instanceof BaseException) { + log.error("【全局异常拦截】DataManagerException: 状态码 {}, 异常信息 {}", ((BaseException) e).getCode(), e.getMessage()); + return ApiResponse.ofException((BaseException) e); + } + + log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage()); + return ApiResponse.ofStatus(Status.ERROR); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java new file mode 100644 index 0000000..d0569cc --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java @@ -0,0 +1,63 @@ +package com.xkcoding.rbac.security.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *

    + * 权限 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:04 + */ +@Data +@Entity +@Table(name = "sec_permission") +public class Permission { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 权限名 + */ + private String name; + + /** + * 类型为页面时,代表前端路由地址,类型为按钮时,代表后端接口地址 + */ + private String url; + + /** + * 权限类型,页面-1,按钮-2 + */ + private Integer type; + + /** + * 权限表达式 + */ + private String permission; + + /** + * 后端接口访问方式 + */ + private String method; + + /** + * 排序 + */ + private Integer sort; + + /** + * 父级id + */ + @Column(name = "parent_id") + private Long parentId; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java new file mode 100644 index 0000000..ed17c0a --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java @@ -0,0 +1,49 @@ +package com.xkcoding.rbac.security.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *

    + * 角色 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 15:45 + */ +@Data +@Entity +@Table(name = "sec_role") +public class Role { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 角色名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 创建时间 + */ + @Column(name = "create_time") + private Long createTime; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private Long updateTime; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java new file mode 100644 index 0000000..21d5e49 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java @@ -0,0 +1,27 @@ +package com.xkcoding.rbac.security.model; + +import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; +import lombok.Data; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + *

    + * 角色-权限 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 13:46 + */ +@Data +@Entity +@Table(name = "sec_role_permission") +public class RolePermission { + /** + * 主键 + */ + @EmbeddedId + private RolePermissionKey id; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java new file mode 100644 index 0000000..ddecc75 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java @@ -0,0 +1,80 @@ +package com.xkcoding.rbac.security.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *

    + * 用户 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:00 + */ +@Data +@Entity +@Table(name = "sec_user") +public class User { + + /** + * 主键 + */ + @Id + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 生日 + */ + private Long birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + /** + * 状态,启用-1,禁用-0 + */ + private Integer status; + + /** + * 创建时间 + */ + @Column(name = "create_time") + private Long createTime; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private Long updateTime; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java new file mode 100644 index 0000000..2ca084d --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java @@ -0,0 +1,27 @@ +package com.xkcoding.rbac.security.model; + +import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; +import lombok.Data; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + *

    + * 用户角色关联 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:18 + */ +@Data +@Entity +@Table(name = "sec_user_role") +public class UserRole { + /** + * 主键 + */ + @EmbeddedId + private UserRoleKey id; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java new file mode 100644 index 0000000..7216c8b --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java @@ -0,0 +1,33 @@ +package com.xkcoding.rbac.security.model.unionkey; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + *

    + * 角色-权限联合主键 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 13:47 + */ +@Data +@Embeddable +public class RolePermissionKey implements Serializable { + private static final long serialVersionUID = 6850974328279713855L; + + /** + * 角色id + */ + @Column(name = "role_id") + private Long roleId; + + /** + * 权限id + */ + @Column(name = "permission_id") + private Long permissionId; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java new file mode 100644 index 0000000..c3c61d5 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java @@ -0,0 +1,33 @@ +package com.xkcoding.rbac.security.model.unionkey; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + *

    + * 用户-角色联合主键 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:20 + */ +@Embeddable +@Data +public class UserRoleKey implements Serializable { + private static final long serialVersionUID = 5633412144183654743L; + + /** + * 用户id + */ + @Column(name = "user_id") + private Long userId; + + /** + * 角色id + */ + @Column(name = "role_id") + private Long roleId; +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java new file mode 100644 index 0000000..92d0baa --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java @@ -0,0 +1,35 @@ +package com.xkcoding.rbac.security.payload; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + *

    + * 登录请求参数 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:52 + */ +@Data +public class LoginRequest { + + /** + * 用户名或邮箱或手机号 + */ + @NotBlank(message = "用户名不能为空") + private String usernameOrEmailOrPhone; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 记住我 + */ + private Boolean rememberMe = false; + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java new file mode 100644 index 0000000..dba46ae --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java @@ -0,0 +1,25 @@ +package com.xkcoding.rbac.security.payload; + +import lombok.Data; + +/** + *

    + * 分页请求参数 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 18:05 + */ +@Data +public class PageCondition { + /** + * 当前页码 + */ + private Integer currentPage; + + /** + * 每页条数 + */ + private Integer pageSize; + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java new file mode 100644 index 0000000..f911dc9 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java @@ -0,0 +1,29 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.model.Permission; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +/** + *

    + * 权限 DAO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:21 + */ +public interface PermissionDao extends JpaRepository, JpaSpecificationExecutor { + + /** + * 根据角色列表查询权限列表 + * + * @param ids 角色id列表 + * @return 权限列表 + */ + @Query(value = "SELECT DISTINCT sec_permission.* FROM sec_permission,sec_role,sec_role_permission WHERE sec_role.id = sec_role_permission.role_id AND sec_permission.id = sec_role_permission.permission_id AND sec_role.id IN (:ids)", nativeQuery = true) + List selectByRoleIdList(@Param("ids") List ids); +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java new file mode 100644 index 0000000..cdb9de9 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java @@ -0,0 +1,28 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +/** + *

    + * 角色 DAO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:20 + */ +public interface RoleDao extends JpaRepository, JpaSpecificationExecutor { + /** + * 根据用户id 查询角色列表 + * + * @param userId 用户id + * @return 角色列表 + */ + @Query(value = "SELECT sec_role.* FROM sec_role,sec_user,sec_user_role WHERE sec_user.id = sec_user_role.user_id AND sec_role.id = sec_user_role.role_id AND sec_user.id = :userId", nativeQuery = true) + List selectByUserId(@Param("userId") Long userId); +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java new file mode 100644 index 0000000..e41a250 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java @@ -0,0 +1,17 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.model.RolePermission; +import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** + *

    + * 角色-权限 DAO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 13:45 + */ +public interface RolePermissionDao extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java new file mode 100644 index 0000000..64cda75 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java @@ -0,0 +1,36 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.List; +import java.util.Optional; + +/** + *

    + * 用户 DAO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 16:18 + */ +public interface UserDao extends JpaRepository, JpaSpecificationExecutor { + /** + * 根据用户名、邮箱、手机号查询用户 + * + * @param username 用户名 + * @param email 邮箱 + * @param phone 手机号 + * @return 用户信息 + */ + Optional findByUsernameOrEmailOrPhone(String username, String email, String phone); + + /** + * 根据用户名列表查询用户列表 + * + * @param usernameList 用户名列表 + * @return 用户列表 + */ + List findByUsernameIn(List usernameList); +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java new file mode 100644 index 0000000..7f0a932 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java @@ -0,0 +1,18 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.model.UserRole; +import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** + *

    + * 用户角色 DAO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:24 + */ +public interface UserRoleDao extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java new file mode 100644 index 0000000..6128c27 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java @@ -0,0 +1,46 @@ +package com.xkcoding.rbac.security.service; + +import com.xkcoding.rbac.security.model.Permission; +import com.xkcoding.rbac.security.model.Role; +import com.xkcoding.rbac.security.model.User; +import com.xkcoding.rbac.security.repository.PermissionDao; +import com.xkcoding.rbac.security.repository.RoleDao; +import com.xkcoding.rbac.security.repository.UserDao; +import com.xkcoding.rbac.security.vo.UserPrincipal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

    + * 自定义UserDetails查询 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 10:29 + */ +@Service +public class CustomUserDetailsService implements UserDetailsService { + @Autowired + private UserDao userDao; + + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Override + public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException { + User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone).orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone)); + List roles = roleDao.selectByUserId(user.getId()); + List roleIds = roles.stream().map(Role::getId).collect(Collectors.toList()); + List permissions = permissionDao.selectByRoleIdList(roleIds); + return UserPrincipal.create(user, roles, permissions); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java new file mode 100644 index 0000000..3acda13 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java @@ -0,0 +1,79 @@ +package com.xkcoding.rbac.security.service; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.PageResult; +import com.xkcoding.rbac.security.model.User; +import com.xkcoding.rbac.security.payload.PageCondition; +import com.xkcoding.rbac.security.repository.UserDao; +import com.xkcoding.rbac.security.util.RedisUtil; +import com.xkcoding.rbac.security.util.SecurityUtil; +import com.xkcoding.rbac.security.vo.OnlineUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

    + * 监控 Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 00:55 + */ +@Slf4j +@Service +public class MonitorService { + @Autowired + private RedisUtil redisUtil; + + @Autowired + private UserDao userDao; + + /** + * 在线用户分页列表 + * + * @param pageCondition 分页参数 + * @return 在线用户分页列表 + */ + public PageResult onlineUser(PageCondition pageCondition) { + PageResult keys = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, pageCondition.getCurrentPage(), pageCondition.getPageSize()); + List rows = keys.getRows(); + Long total = keys.getTotal(); + + // 根据 redis 中键获取用户名列表 + List usernameList = rows.stream().map(s -> StrUtil.subAfter(s, Consts.REDIS_JWT_KEY_PREFIX, true)).collect(Collectors.toList()); + // 根据用户名查询用户信息 + List userList = userDao.findByUsernameIn(usernameList); + + // 封装在线用户信息 + List onlineUserList = Lists.newArrayList(); + userList.forEach(user -> onlineUserList.add(OnlineUser.create(user))); + + return new PageResult<>(onlineUserList, total); + } + + /** + * 踢出在线用户 + * + * @param names 用户名列表 + */ + public void kickout(List names) { + // 清除 Redis 中的 JWT 信息 + List redisKeys = names.parallelStream().map(s -> Consts.REDIS_JWT_KEY_PREFIX + s).collect(Collectors.toList()); + redisUtil.delete(redisKeys); + + // 获取当前用户名 + String currentUsername = SecurityUtil.getCurrentUsername(); + names.parallelStream().forEach(name -> { + // TODO: 通知被踢出的用户已被当前登录用户踢出, + // 后期考虑使用 websocket 实现,具体伪代码实现如下。 + // String message = "您已被用户【" + currentUsername + "】手动下线!"; + log.debug("用户【{}】被用户【{}】手动下线!", name, currentUsername); + }); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java new file mode 100644 index 0000000..1a5b366 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java @@ -0,0 +1,162 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.Status; +import com.xkcoding.rbac.security.config.JwtConfig; +import com.xkcoding.rbac.security.exception.SecurityException; +import com.xkcoding.rbac.security.vo.UserPrincipal; +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + *

    + * JWT 工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 13:42 + */ +@EnableConfigurationProperties(JwtConfig.class) +@Configuration +@Slf4j +public class JwtUtil { + @Autowired + private JwtConfig jwtConfig; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 创建JWT + * + * @param rememberMe 记住我 + * @param id 用户id + * @param subject 用户名 + * @param roles 用户角色 + * @param authorities 用户权限 + * @return JWT + */ + public String createJWT(Boolean rememberMe, Long id, String subject, List roles, Collection authorities) { + Date now = new Date(); + JwtBuilder builder = Jwts.builder().setId(id.toString()).setSubject(subject).setIssuedAt(now).signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()).claim("roles", roles).claim("authorities", authorities); + + // 设置过期时间 + Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); + if (ttl > 0) { + builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue())); + } + + String jwt = builder.compact(); + // 将生成的JWT保存至Redis + stringRedisTemplate.opsForValue().set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS); + return jwt; + } + + /** + * 创建JWT + * + * @param authentication 用户认证信息 + * @param rememberMe 记住我 + * @return JWT + */ + public String createJWT(Authentication authentication, Boolean rememberMe) { + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); + } + + /** + * 解析JWT + * + * @param jwt JWT + * @return {@link Claims} + */ + public Claims parseJWT(String jwt) { + try { + Claims claims = Jwts.parser().setSigningKey(jwtConfig.getKey()).parseClaimsJws(jwt).getBody(); + + String username = claims.getSubject(); + String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username; + + // 校验redis中的JWT是否存在 + Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); + if (Objects.isNull(expire) || expire <= 0) { + throw new SecurityException(Status.TOKEN_EXPIRED); + } + + // 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期 + String redisToken = stringRedisTemplate.opsForValue().get(redisKey); + if (!StrUtil.equals(jwt, redisToken)) { + throw new SecurityException(Status.TOKEN_OUT_OF_CTRL); + } + return claims; + } catch (ExpiredJwtException e) { + log.error("Token 已过期"); + throw new SecurityException(Status.TOKEN_EXPIRED); + } catch (UnsupportedJwtException e) { + log.error("不支持的 Token"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (MalformedJwtException e) { + log.error("Token 无效"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (SignatureException e) { + log.error("无效的 Token 签名"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } catch (IllegalArgumentException e) { + log.error("Token 参数不存在"); + throw new SecurityException(Status.TOKEN_PARSE_ERROR); + } + } + + /** + * 设置JWT过期 + * + * @param request 请求 + */ + public void invalidateJWT(HttpServletRequest request) { + String jwt = getJwtFromRequest(request); + String username = getUsernameFromJWT(jwt); + // 从redis中清除JWT + stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username); + } + + /** + * 根据 jwt 获取用户名 + * + * @param jwt JWT + * @return 用户名 + */ + public String getUsernameFromJWT(String jwt) { + Claims claims = parseJWT(jwt); + return claims.getSubject(); + } + + /** + * 从 request 的 header 中获取 JWT + * + * @param request 请求 + * @return JWT + */ + public String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java new file mode 100644 index 0000000..a676061 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java @@ -0,0 +1,48 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.payload.PageCondition; +import org.springframework.data.domain.PageRequest; + +/** + *

    + * 分页工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 18:09 + */ +public class PageUtil { + /** + * 校验分页参数,为NULL,设置分页参数默认值 + * + * @param condition 查询参数 + * @param clazz 类 + * @param {@link PageCondition} + */ + public static void checkPageCondition(T condition, Class clazz) { + if (ObjectUtil.isNull(condition)) { + condition = ReflectUtil.newInstance(clazz); + } + // 校验分页参数 + if (ObjectUtil.isNull(condition.getCurrentPage())) { + condition.setCurrentPage(Consts.DEFAULT_CURRENT_PAGE); + } + if (ObjectUtil.isNull(condition.getPageSize())) { + condition.setPageSize(Consts.DEFAULT_PAGE_SIZE); + } + } + + /** + * 根据分页参数构建{@link PageRequest} + * + * @param condition 查询参数 + * @param {@link PageCondition} + * @return {@link PageRequest} + */ + public static PageRequest ofPageRequest(T condition) { + return PageRequest.of(condition.getCurrentPage(), condition.getPageSize()); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java new file mode 100644 index 0000000..3a7c842 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java @@ -0,0 +1,86 @@ +package com.xkcoding.rbac.security.util; + +import com.google.common.collect.Lists; +import com.xkcoding.rbac.security.common.PageResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + *

    + * Redis工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 20:24 + */ +@Component +@Slf4j +public class RedisUtil { + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率 + * + * @param patternKey key格式 + * @param currentPage 当前页码 + * @param pageSize 每页条数 + * @return 分页获取指定格式key + */ + public PageResult findKeysForPage(String patternKey, int currentPage, int pageSize) { + ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); + RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); + RedisConnection rc = factory.getConnection(); + Cursor cursor = rc.scan(options); + + List result = Lists.newArrayList(); + + long tmpIndex = 0; + int startIndex = (currentPage - 1) * pageSize; + int end = currentPage * pageSize; + while (cursor.hasNext()) { + String key = new String(cursor.next()); + if (tmpIndex >= startIndex && tmpIndex < end) { + result.add(key); + } + tmpIndex++; + } + + try { + cursor.close(); + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.warn("Redis连接关闭异常,", e); + } + + return new PageResult<>(result, tmpIndex); + } + + /** + * 删除 Redis 中的某个key + * + * @param key 键 + */ + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 批量删除 Redis 中的某些key + * + * @param keys 键列表 + */ + public void delete(Collection keys) { + stringRedisTemplate.delete(keys); + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java new file mode 100644 index 0000000..a4e8500 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java @@ -0,0 +1,66 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.BaseException; +import com.xkcoding.rbac.security.common.IStatus; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

    + * Response 通用工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-07 17:37 + */ +@Slf4j +public class ResponseUtil { + + /** + * 往 response 写出 json + * + * @param response 响应 + * @param status 状态 + * @param data 返回数据 + */ + public static void renderJson(HttpServletResponse response, IStatus status, Object data) { + try { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(200); + + // FIXME: hutool 的 BUG:JSONUtil.toJsonStr() + // 将JSON转为String的时候,忽略null值的时候转成的String存在错误 + response.getWriter().write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofStatus(status, data), false))); + } catch (IOException e) { + log.error("Response写出JSON异常,", e); + } + } + + /** + * 往 response 写出 json + * + * @param response 响应 + * @param exception 异常 + */ + public static void renderJson(HttpServletResponse response, BaseException exception) { + try { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(200); + + // FIXME: hutool 的 BUG:JSONUtil.toJsonStr() + // 将JSON转为String的时候,忽略null值的时候转成的String存在错误 + response.getWriter().write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofException(exception), false))); + } catch (IOException e) { + log.error("Response写出JSON异常,", e); + } + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java new file mode 100644 index 0000000..be3fde6 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java @@ -0,0 +1,40 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.vo.UserPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +/** + *

    + * Spring Security工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 18:30 + */ +public class SecurityUtil { + /** + * 获取当前登录用户用户名 + * + * @return 当前登录用户用户名 + */ + public static String getCurrentUsername() { + UserPrincipal currentUser = getCurrentUser(); + return ObjectUtil.isNull(currentUser) ? Consts.ANONYMOUS_NAME : currentUser.getUsername(); + } + + /** + * 获取当前登录用户信息 + * + * @return 当前登录用户信息,匿名登录时,为null + */ + public static UserPrincipal getCurrentUser() { + Object userInfo = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (userInfo instanceof UserDetails) { + return (UserPrincipal) userInfo; + } + return null; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java new file mode 100644 index 0000000..c052f04 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java @@ -0,0 +1,31 @@ +package com.xkcoding.rbac.security.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

    + * JWT 响应返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 16:01 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JwtResponse { + /** + * token 字段 + */ + private String token; + /** + * token类型 + */ + private String tokenType = "Bearer"; + + public JwtResponse(String token) { + this.token = token; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java new file mode 100644 index 0000000..190a8fe --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java @@ -0,0 +1,63 @@ +package com.xkcoding.rbac.security.vo; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.model.User; +import lombok.Data; + +/** + *

    + * 在线用户 VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 00:58 + */ +@Data +public class OnlineUser { + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 生日 + */ + private Long birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + public static OnlineUser create(User user) { + OnlineUser onlineUser = new OnlineUser(); + BeanUtil.copyProperties(user, onlineUser); + // 脱敏 + onlineUser.setPhone(StrUtil.hide(user.getPhone(), 3, 7)); + onlineUser.setEmail(StrUtil.hide(user.getEmail(), 1, StrUtil.indexOfIgnoreCase(user.getEmail(), Consts.SYMBOL_EMAIL))); + return onlineUser; + } +} diff --git a/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java new file mode 100644 index 0000000..1cf9c41 --- /dev/null +++ b/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java @@ -0,0 +1,141 @@ +package com.xkcoding.rbac.security.vo; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.model.Permission; +import com.xkcoding.rbac.security.model.Role; +import com.xkcoding.rbac.security.model.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + *

    + * 自定义User + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 15:09 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserPrincipal implements UserDetails { + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + @JsonIgnore + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 生日 + */ + private Long birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + /** + * 状态,启用-1,禁用-0 + */ + private Integer status; + + /** + * 创建时间 + */ + private Long createTime; + + /** + * 更新时间 + */ + private Long updateTime; + + /** + * 用户角色列表 + */ + private List roles; + + /** + * 用户权限列表 + */ + private Collection authorities; + + public static UserPrincipal create(User user, List roles, List permissions) { + List roleNames = roles.stream().map(Role::getName).collect(Collectors.toList()); + + List authorities = permissions.stream().filter(permission -> StrUtil.isNotBlank(permission.getPermission())).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList()); + + return new UserPrincipal(user.getId(), user.getUsername(), user.getPassword(), user.getNickname(), user.getPhone(), user.getEmail(), user.getBirthday(), user.getSex(), user.getStatus(), user.getCreateTime(), user.getUpdateTime(), roleNames, authorities); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return Objects.equals(this.status, Consts.ENABLE); + } +} diff --git a/spring-boot-demo-rbac-security/src/main/resources/application.yml b/demo-rbac-security/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-rbac-security/src/main/resources/application.yml rename to demo-rbac-security/src/main/resources/application.yml diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplicationTests.java b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplicationTests.java similarity index 100% rename from spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplicationTests.java rename to demo-rbac-security/src/test/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplicationTests.java diff --git a/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java new file mode 100644 index 0000000..b54f98c --- /dev/null +++ b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java @@ -0,0 +1,139 @@ +package com.xkcoding.rbac.security.repository; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Snowflake; +import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; +import com.xkcoding.rbac.security.model.*; +import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; +import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + *

    + * 数据初始化测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-10 11:26 + */ +public class DataInitTest extends SpringBootDemoRbacSecurityApplicationTests { + @Autowired + private UserDao userDao; + + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Autowired + private UserRoleDao userRoleDao; + + @Autowired + private RolePermissionDao rolePermissionDao; + + @Autowired + private Snowflake snowflake; + + @Autowired + private BCryptPasswordEncoder encoder; + + @Test + public void initTest() { + init(); + } + + private void init() { + User admin = createUser(true); + User user = createUser(false); + + Role roleAdmin = createRole(true); + Role roleUser = createRole(false); + + createUserRoleRelation(admin.getId(), roleAdmin.getId()); + createUserRoleRelation(user.getId(), roleUser.getId()); + + // 页面权限 + Permission testPagePerm = createPermission("/test", "测试页面", 1, "page:test", null, 1, 0L); + // 按钮权限 + Permission testBtnQueryPerm = createPermission("/**/test", "测试页面-查询", 2, "btn:test:query", "GET", 1, testPagePerm.getId()); + Permission testBtnPermInsert = createPermission("/**/test", "测试页面-添加", 2, "btn:test:insert", "POST", 2, testPagePerm.getId()); + + Permission monitorOnlinePagePerm = createPermission("/monitor", "监控在线用户页面", 1, "page:monitor:online", null, 2, 0L); + Permission monitorOnlineBtnQueryPerm = createPermission("/**/api/monitor/online/user", "在线用户页面-查询", 2, "btn:monitor:online:query", "GET", 1, monitorOnlinePagePerm.getId()); + Permission monitorOnlineBtnKickoutPerm = createPermission("/**/api/monitor/online/user/kickout", "在线用户页面-踢出", 2, "btn:monitor:online:kickout", "DELETE", 2, monitorOnlinePagePerm.getId()); + + createRolePermissionRelation(roleAdmin.getId(), testPagePerm.getId()); + createRolePermissionRelation(roleUser.getId(), testPagePerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), testBtnQueryPerm.getId()); + createRolePermissionRelation(roleUser.getId(), testBtnQueryPerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), testBtnPermInsert.getId()); + createRolePermissionRelation(roleAdmin.getId(), monitorOnlinePagePerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), monitorOnlineBtnQueryPerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), monitorOnlineBtnKickoutPerm.getId()); + } + + private void createRolePermissionRelation(Long roleId, Long permissionId) { + RolePermission adminPage = new RolePermission(); + RolePermissionKey adminPageKey = new RolePermissionKey(); + adminPageKey.setRoleId(roleId); + adminPageKey.setPermissionId(permissionId); + adminPage.setId(adminPageKey); + rolePermissionDao.save(adminPage); + } + + private Permission createPermission(String url, String name, Integer type, String permission, String method, Integer sort, Long parentId) { + Permission perm = new Permission(); + perm.setId(snowflake.nextId()); + perm.setUrl(url); + perm.setName(name); + perm.setType(type); + perm.setPermission(permission); + perm.setMethod(method); + perm.setSort(sort); + perm.setParentId(parentId); + permissionDao.save(perm); + return perm; + } + + private void createUserRoleRelation(Long userId, Long roleId) { + UserRole userRole = new UserRole(); + UserRoleKey key = new UserRoleKey(); + key.setUserId(userId); + key.setRoleId(roleId); + userRole.setId(key); + userRoleDao.save(userRole); + } + + private Role createRole(boolean isAdmin) { + Role role = new Role(); + role.setId(snowflake.nextId()); + role.setName(isAdmin ? "管理员" : "普通用户"); + role.setDescription(isAdmin ? "超级管理员" : "普通用户"); + role.setCreateTime(DateUtil.current(false)); + role.setUpdateTime(DateUtil.current(false)); + roleDao.save(role); + return role; + } + + private User createUser(boolean isAdmin) { + User user = new User(); + user.setId(snowflake.nextId()); + user.setUsername(isAdmin ? "admin" : "user"); + user.setNickname(isAdmin ? "管理员" : "普通用户"); + user.setPassword(encoder.encode("123456")); + user.setBirthday(DateTime.of("1994-11-22", "yyyy-MM-dd").getTime()); + user.setEmail((isAdmin ? "admin" : "user") + "@xkcoding.com"); + user.setPhone(isAdmin ? "17300000000" : "17300001111"); + user.setSex(1); + user.setStatus(1); + user.setCreateTime(DateUtil.current(false)); + user.setUpdateTime(DateUtil.current(false)); + userDao.save(user); + return user; + } + +} diff --git a/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java new file mode 100644 index 0000000..d05d2bc --- /dev/null +++ b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java @@ -0,0 +1,33 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; +import com.xkcoding.rbac.security.model.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + *

    + * UserDao 测试 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 01:10 + */ +@Slf4j +public class UserDaoTest extends SpringBootDemoRbacSecurityApplicationTests { + @Autowired + private UserDao userDao; + + @Test + public void findByUsernameIn() { + List usernameList = Lists.newArrayList("admin", "user"); + List userList = userDao.findByUsernameIn(usernameList); + Assert.assertEquals(2, userList.size()); + log.info("【userList】= {}", userList); + } +} diff --git a/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java new file mode 100644 index 0000000..7706f93 --- /dev/null +++ b/demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java @@ -0,0 +1,29 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.json.JSONUtil; +import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.PageResult; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

    + * 测试RedisUtil + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-11 20:44 + */ +@Slf4j +public class RedisUtilTest extends SpringBootDemoRbacSecurityApplicationTests { + @Autowired + private RedisUtil redisUtil; + + @Test + public void findKeysForPage() { + PageResult pageResult = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, 2, 1); + log.info("【pageResult】= {}", JSONUtil.toJsonStr(pageResult)); + } +} diff --git a/spring-boot-demo-session/.gitignore b/demo-rbac-shiro/.gitignore similarity index 100% rename from spring-boot-demo-session/.gitignore rename to demo-rbac-shiro/.gitignore diff --git a/demo-rbac-shiro/pom.xml b/demo-rbac-shiro/pom.xml new file mode 100644 index 0000000..e600582 --- /dev/null +++ b/demo-rbac-shiro/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + demo-rbac-shiro + 1.0.0-SNAPSHOT + jar + + demo-rbac-shiro + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + mybatis-plus-boot-starter + 3.1.0 + + + + p6spy + p6spy + 3.8.1 + + + + + org.apache.shiro + shiro-spring-boot-starter + 1.4.0 + + + + mysql + mysql-connector-java + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-rbac-shiro + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-rbac-shiro/sql/shiro.sql b/demo-rbac-shiro/sql/shiro.sql similarity index 100% rename from spring-boot-demo-rbac-shiro/sql/shiro.sql rename to demo-rbac-shiro/sql/shiro.sql diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java new file mode 100644 index 0000000..df7c6b1 --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.rbac.shiro; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 16:11 + */ +@SpringBootApplication +@MapperScan("com.xkcoding.rbac.shiro.mapper") +public class SpringBootDemoRbacShiroApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoRbacShiroApplication.class, args); + } +} diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java new file mode 100644 index 0000000..17ff7ba --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java @@ -0,0 +1,25 @@ +package com.xkcoding.rbac.shiro.common; + +/** + *

    + * 统一状态码接口 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 16:28 + */ +public interface IResultCode { + /** + * 获取状态码 + * + * @return 状态码 + */ + Integer getCode(); + + /** + * 获取返回消息 + * + * @return 返回消息 + */ + String getMessage(); +} diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java new file mode 100644 index 0000000..4cd5f00 --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java @@ -0,0 +1,90 @@ +package com.xkcoding.rbac.shiro.common; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

    + * 统一API对象返回 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 16:24 + */ +@Data +@NoArgsConstructor +public class R { + /** + * 状态码 + */ + private Integer code; + + /** + * 返回消息 + */ + private String message; + + /** + * 状态 + */ + private boolean status; + + /** + * 返回数据 + */ + private T data; + + public R(Integer code, String message, boolean status, T data) { + this.code = code; + this.message = message; + this.status = status; + this.data = data; + } + + public R(IResultCode resultCode, boolean status, T data) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + this.status = status; + this.data = data; + } + + public R(IResultCode resultCode, boolean status) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + this.status = status; + this.data = null; + } + + public static R success() { + return new R<>(ResultCode.OK, true); + } + + public static R message(String message) { + return new R<>(ResultCode.OK.getCode(), message, true, null); + } + + public static R success(T data) { + return new R<>(ResultCode.OK, true, data); + } + + public static R fail() { + return new R<>(ResultCode.ERROR, false); + } + + public static R fail(IResultCode resultCode) { + return new R<>(resultCode, false); + } + + public static R fail(Integer code, String message) { + return new R<>(code, message, false, null); + } + + public static R fail(IResultCode resultCode, T data) { + return new R<>(resultCode, false, data); + } + + public static R fail(Integer code, String message, T data) { + return new R<>(code, message, false, data); + } + +} diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java new file mode 100644 index 0000000..1f0236f --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java @@ -0,0 +1,39 @@ +package com.xkcoding.rbac.shiro.common; + +import lombok.Getter; + +/** + *

    + * 通用状态枚举 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 16:31 + */ +@Getter +public enum ResultCode implements IResultCode { + /** + * 成功 + */ + OK(200, "成功"), + /** + * 失败 + */ + ERROR(500, "失败"); + + /** + * 返回码 + */ + private Integer code; + + /** + * 返回消息 + */ + private String message; + + ResultCode(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java new file mode 100644 index 0000000..9cf6d4e --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java @@ -0,0 +1,43 @@ +package com.xkcoding.rbac.shiro.config; + +import com.baomidou.mybatisplus.core.parser.ISqlParser; +import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser; +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + *

    + * MP3 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 17:06 + */ +@Configuration +public class MybatisPlusConfig { + + @Bean + public PaginationInterceptor paginationInterceptor() { + PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); + + List sqlParserList = new ArrayList<>(); + // 攻击 SQL 阻断解析器、加入解析链 + sqlParserList.add(new BlockAttackSqlParser()); + paginationInterceptor.setSqlParserList(sqlParserList); + + return paginationInterceptor; + } + + /** + * SQL执行效率插件 + */ + @Bean + public PerformanceInterceptor performanceInterceptor() { + return new PerformanceInterceptor(); + } +} diff --git a/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java new file mode 100644 index 0000000..654f7cd --- /dev/null +++ b/demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java @@ -0,0 +1,24 @@ +package com.xkcoding.rbac.shiro.controller; + +import com.xkcoding.rbac.shiro.common.R; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 测试Controller + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-21 16:13 + */ +@RestController +@RequestMapping("/test") +public class TestController { + + @GetMapping("") + public R test() { + return R.success(); + } +} diff --git a/spring-boot-demo-rbac-shiro/src/main/resources/application.yml b/demo-rbac-shiro/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-rbac-shiro/src/main/resources/application.yml rename to demo-rbac-shiro/src/main/resources/application.yml diff --git a/spring-boot-demo-rbac-shiro/src/main/resources/spy.properties b/demo-rbac-shiro/src/main/resources/spy.properties similarity index 100% rename from spring-boot-demo-rbac-shiro/src/main/resources/spy.properties rename to demo-rbac-shiro/src/main/resources/spy.properties diff --git a/spring-boot-demo-rbac-shiro/src/test/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplicationTests.java b/demo-rbac-shiro/src/test/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplicationTests.java similarity index 100% rename from spring-boot-demo-rbac-shiro/src/test/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplicationTests.java rename to demo-rbac-shiro/src/test/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplicationTests.java diff --git a/spring-boot-demo-sharding-jdbc/.gitignore b/demo-session/.gitignore similarity index 100% rename from spring-boot-demo-sharding-jdbc/.gitignore rename to demo-session/.gitignore diff --git a/spring-boot-demo-session/README.md b/demo-session/README.md similarity index 100% rename from spring-boot-demo-session/README.md rename to demo-session/README.md diff --git a/demo-session/pom.xml b/demo-session/pom.xml new file mode 100644 index 0000000..e2f73f6 --- /dev/null +++ b/demo-session/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + demo-session + 1.0.0-SNAPSHOT + jar + + demo-session + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.session + spring-session-data-redis + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + + demo-session + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java b/demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java new file mode 100644 index 0000000..e22efcc --- /dev/null +++ b/demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.session; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 19:35 + */ +@SpringBootApplication +public class SpringBootDemoSessionApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoSessionApplication.class, args); + } + +} + diff --git a/demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java b/demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java new file mode 100644 index 0000000..0a5f79b --- /dev/null +++ b/demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java @@ -0,0 +1,34 @@ +package com.xkcoding.session.config; + +import com.xkcoding.session.interceptor.SessionInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + *

    + * WebMvc 配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 19:50 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + @Autowired + private SessionInterceptor sessionInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + InterceptorRegistration sessionInterceptorRegistry = registry.addInterceptor(sessionInterceptor); + // 排除不需要拦截的路径 + sessionInterceptorRegistry.excludePathPatterns("/page/login"); + sessionInterceptorRegistry.excludePathPatterns("/page/doLogin"); + sessionInterceptorRegistry.excludePathPatterns("/error"); + + // 需要拦截的路径 + sessionInterceptorRegistry.addPathPatterns("/**"); + } +} diff --git a/demo-session/src/main/java/com/xkcoding/session/constants/Consts.java b/demo-session/src/main/java/com/xkcoding/session/constants/Consts.java new file mode 100644 index 0000000..d8a28fe --- /dev/null +++ b/demo-session/src/main/java/com/xkcoding/session/constants/Consts.java @@ -0,0 +1,16 @@ +package com.xkcoding.session.constants; + +/** + *

    + * 常量池 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 19:42 + */ +public interface Consts { + /** + * session保存的key + */ + String SESSION_KEY = "key:session:token"; +} diff --git a/demo-session/src/main/java/com/xkcoding/session/controller/PageController.java b/demo-session/src/main/java/com/xkcoding/session/controller/PageController.java new file mode 100644 index 0000000..64cf929 --- /dev/null +++ b/demo-session/src/main/java/com/xkcoding/session/controller/PageController.java @@ -0,0 +1,62 @@ +package com.xkcoding.session.controller; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.session.constants.Consts; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +/** + *

    + * 页面跳转 Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 19:57 + */ +@Controller +@RequestMapping("/page") +public class PageController { + /** + * 跳转到 首页 + * + * @param request 请求 + */ + @GetMapping("/index") + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + String token = (String) request.getSession().getAttribute(Consts.SESSION_KEY); + mv.setViewName("index"); + mv.addObject("token", token); + return mv; + } + + /** + * 跳转到 登录页 + * + * @param redirect 是否是跳转回来的 + */ + @GetMapping("/login") + public ModelAndView login(Boolean redirect) { + ModelAndView mv = new ModelAndView(); + + if (ObjectUtil.isNotNull(redirect) && ObjectUtil.equal(true, redirect)) { + mv.addObject("message", "请先登录!"); + } + mv.setViewName("login"); + return mv; + } + + @GetMapping("/doLogin") + public String doLogin(HttpSession session) { + session.setAttribute(Consts.SESSION_KEY, IdUtil.fastUUID()); + + return "redirect:/page/index"; + } +} diff --git a/demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java b/demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java new file mode 100644 index 0000000..204106d --- /dev/null +++ b/demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java @@ -0,0 +1,32 @@ +package com.xkcoding.session.interceptor; + +import com.xkcoding.session.constants.Consts; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + *

    + * 校验Session的拦截器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 19:40 + */ +@Component +public class SessionInterceptor extends HandlerInterceptorAdapter { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(); + if (session.getAttribute(Consts.SESSION_KEY) != null) { + return true; + } + // 跳转到登录页 + String url = "/page/login?redirect=true"; + response.sendRedirect(request.getContextPath() + url); + return false; + } +} diff --git a/spring-boot-demo-session/src/main/resources/application.yml b/demo-session/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-session/src/main/resources/application.yml rename to demo-session/src/main/resources/application.yml diff --git a/spring-boot-demo-session/src/main/resources/templates/index.html b/demo-session/src/main/resources/templates/index.html similarity index 100% rename from spring-boot-demo-session/src/main/resources/templates/index.html rename to demo-session/src/main/resources/templates/index.html diff --git a/spring-boot-demo-session/src/main/resources/templates/login.html b/demo-session/src/main/resources/templates/login.html similarity index 100% rename from spring-boot-demo-session/src/main/resources/templates/login.html rename to demo-session/src/main/resources/templates/login.html diff --git a/spring-boot-demo-session/src/test/java/com/xkcoding/session/SpringBootDemoSessionApplicationTests.java b/demo-session/src/test/java/com/xkcoding/session/SpringBootDemoSessionApplicationTests.java similarity index 100% rename from spring-boot-demo-session/src/test/java/com/xkcoding/session/SpringBootDemoSessionApplicationTests.java rename to demo-session/src/test/java/com/xkcoding/session/SpringBootDemoSessionApplicationTests.java diff --git a/spring-boot-demo-social/.gitignore b/demo-sharding-jdbc/.gitignore similarity index 100% rename from spring-boot-demo-social/.gitignore rename to demo-sharding-jdbc/.gitignore diff --git a/demo-sharding-jdbc/README.md b/demo-sharding-jdbc/README.md new file mode 100644 index 0000000..733d7df --- /dev/null +++ b/demo-sharding-jdbc/README.md @@ -0,0 +1,280 @@ +# spring-boot-demo-sharding-jdbc + +> 本 demo 主要演示了如何集成 `sharding-jdbc` 实现分库分表操作,ORM 层使用了`Mybatis-Plus`简化开发,童鞋们可以按照自己的喜好替换为 JPA、通用Mapper、JdbcTemplate甚至原生的JDBC都可以。 +> +> PS: +> +> 1. 目前当当官方提供的starter存在bug,版本号:`3.1.0`,因此本demo采用手动配置。 +> 2. 文档真的很垃圾​ :joy: + +## 1. 运行方式 + +1. 在数据库创建2个数据库,分别为:`spring-boot-demo`、`spring-boot-demo-2` +2. 去数据库执行 `sql/schema.sql` ,创建 `6` 张分片表 +3. 找到 `DataSourceShardingConfig` 配置类,修改 `数据源` 的相关配置,位于 `dataSourceMap()` 这个方法 +4. 找到测试类 `SpringBootDemoShardingJdbcApplicationTests` 进行测试 + +## 2. 关键代码 + +### 2.1. `pom.xml` + +```xml + + + 4.0.0 + + spring-boot-demo-sharding-jdbc + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-sharding-jdbc + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + mybatis-plus-boot-starter + 3.1.0 + + + + mysql + mysql-connector-java + + + + io.shardingsphere + sharding-jdbc-core + 3.1.0 + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-sharding-jdbc + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 2.2. `CustomSnowflakeKeyGenerator.java` + +```java +package com.xkcoding.sharding.jdbc.config; + +import cn.hutool.core.lang.Snowflake; +import io.shardingsphere.core.keygen.KeyGenerator; + +/** + *

    + * 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 17:07 + */ +public class CustomSnowflakeKeyGenerator implements KeyGenerator { + private Snowflake snowflake; + + public CustomSnowflakeKeyGenerator(Snowflake snowflake) { + this.snowflake = snowflake; + } + + @Override + public Number generateKey() { + return snowflake.nextId(); + } +} +``` + +### 2.3. `DataSourceShardingConfig.java` + +```java +/** + *

    + * sharding-jdbc 的数据源配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 16:47 + */ +@Configuration +public class DataSourceShardingConfig { + private static final Snowflake snowflake = IdUtil.createSnowflake(1, 1); + + /** + * 需要手动配置事务管理器 + */ + @Bean + public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean(name = "dataSource") + @Primary + public DataSource dataSource() throws SQLException { + ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); + // 设置分库策略 + shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")); + // 设置规则适配的表 + shardingRuleConfig.getBindingTableGroups().add("t_order"); + // 设置分表策略 + shardingRuleConfig.getTableRuleConfigs().add(orderTableRule()); + shardingRuleConfig.setDefaultDataSourceName("ds0"); + shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration()); + + Properties properties = new Properties(); + properties.setProperty("sql.show", "true"); + + return ShardingDataSourceFactory.createDataSource(dataSourceMap(), shardingRuleConfig, new ConcurrentHashMap<>(16), properties); + } + + private TableRuleConfiguration orderTableRule() { + TableRuleConfiguration tableRule = new TableRuleConfiguration(); + // 设置逻辑表名 + tableRule.setLogicTable("t_order"); + // ds${0..1}.t_order_${0..2} 也可以写成 ds$->{0..1}.t_order_$->{0..1} + tableRule.setActualDataNodes("ds${0..1}.t_order_${0..2}"); + tableRule.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_$->{order_id % 3}")); + tableRule.setKeyGenerator(customKeyGenerator()); + tableRule.setKeyGeneratorColumnName("order_id"); + return tableRule; + } + + private Map dataSourceMap() { + Map dataSourceMap = new HashMap<>(16); + + // 配置第一个数据源 + HikariDataSource ds0 = new HikariDataSource(); + ds0.setDriverClassName("com.mysql.cj.jdbc.Driver"); + ds0.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); + ds0.setUsername("root"); + ds0.setPassword("root"); + + // 配置第二个数据源 + HikariDataSource ds1 = new HikariDataSource(); + ds1.setDriverClassName("com.mysql.cj.jdbc.Driver"); + ds1.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); + ds1.setUsername("root"); + ds1.setPassword("root"); + + dataSourceMap.put("ds0", ds0); + dataSourceMap.put("ds1", ds1); + return dataSourceMap; + } + + /** + * 自定义主键生成器 + */ + private KeyGenerator customKeyGenerator() { + return new CustomSnowflakeKeyGenerator(snowflake); + } + +} +``` + +### 2.3. `SpringBootDemoShardingJdbcApplicationTests.java` + +```java +/** + *

    + * 测试sharding-jdbc分库分表 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 13:44 + */ +@Slf4j +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoShardingJdbcApplicationTests { + @Autowired + private OrderMapper orderMapper; + + /** + * 测试新增 + */ + @Test + public void testInsert() { + for (long i = 1; i < 10; i++) { + for (long j = 1; j < 20; j++) { + Order order = Order.builder().userId(i).orderId(j).remark(RandomUtil.randomString(20)).build(); + orderMapper.insert(order); + } + } + } + + /** + * 测试更新 + */ + @Test + public void testUpdate() { + Order update = new Order(); + update.setRemark("修改备注信息"); + orderMapper.update(update, Wrappers.update().lambda().eq(Order::getOrderId, 2).eq(Order::getUserId, 2)); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + orderMapper.delete(new QueryWrapper<>()); + } + + /** + * 测试查询 + */ + @Test + public void testSelect() { + List orders = orderMapper.selectList(Wrappers.query().lambda().in(Order::getOrderId, 1, 2)); + log.info("【orders】= {}", JSONUtil.toJsonStr(orders)); + } + +} +``` + +## 3. 参考 + +1. `ShardingSphere` 官网:https://shardingsphere.apache.org/index_zh.html (虽然文档确实垃圾,但是还是得参考啊~) +2. `Mybatis-Plus` 语法参考官网:https://mybatis.plus/ diff --git a/demo-sharding-jdbc/pom.xml b/demo-sharding-jdbc/pom.xml new file mode 100644 index 0000000..bceb356 --- /dev/null +++ b/demo-sharding-jdbc/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + demo-sharding-jdbc + 1.0.0-SNAPSHOT + jar + + demo-sharding-jdbc + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + mybatis-plus-boot-starter + 3.1.0 + + + + mysql + mysql-connector-java + + + + io.shardingsphere + sharding-jdbc-core + 3.1.0 + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + demo-sharding-jdbc + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-sharding-jdbc/sql/schema.sql b/demo-sharding-jdbc/sql/schema.sql similarity index 100% rename from spring-boot-demo-sharding-jdbc/sql/schema.sql rename to demo-sharding-jdbc/sql/schema.sql diff --git a/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java new file mode 100644 index 0000000..363dbd7 --- /dev/null +++ b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java @@ -0,0 +1,26 @@ +package com.xkcoding.sharding.jdbc; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-01-23 22:05 + */ +@SpringBootApplication +@EnableTransactionManagement(proxyTargetClass = true) +@MapperScan("com.xkcoding.sharding.jdbc.mapper") +public class SpringBootDemoShardingJdbcApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoShardingJdbcApplication.class, args); + } + +} + diff --git a/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java new file mode 100644 index 0000000..667c4b0 --- /dev/null +++ b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java @@ -0,0 +1,25 @@ +package com.xkcoding.sharding.jdbc.config; + +import cn.hutool.core.lang.Snowflake; +import io.shardingsphere.core.keygen.KeyGenerator; + +/** + *

    + * 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 17:07 + */ +public class CustomSnowflakeKeyGenerator implements KeyGenerator { + private Snowflake snowflake; + + public CustomSnowflakeKeyGenerator(Snowflake snowflake) { + this.snowflake = snowflake; + } + + @Override + public Number generateKey() { + return snowflake.nextId(); + } +} diff --git a/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java new file mode 100644 index 0000000..9aaecaf --- /dev/null +++ b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java @@ -0,0 +1,105 @@ +package com.xkcoding.sharding.jdbc.config; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import com.zaxxer.hikari.HikariDataSource; +import io.shardingsphere.api.config.rule.ShardingRuleConfiguration; +import io.shardingsphere.api.config.rule.TableRuleConfiguration; +import io.shardingsphere.api.config.strategy.InlineShardingStrategyConfiguration; +import io.shardingsphere.api.config.strategy.NoneShardingStrategyConfiguration; +import io.shardingsphere.core.keygen.KeyGenerator; +import io.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

    + * sharding-jdbc 的数据源配置 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 16:47 + */ +@Configuration +public class DataSourceShardingConfig { + private static final Snowflake snowflake = IdUtil.createSnowflake(1, 1); + + /** + * 需要手动配置事务管理器 + */ + @Bean + public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean(name = "dataSource") + @Primary + public DataSource dataSource() throws SQLException { + ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); + // 设置分库策略 + shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")); + // 设置规则适配的表 + shardingRuleConfig.getBindingTableGroups().add("t_order"); + // 设置分表策略 + shardingRuleConfig.getTableRuleConfigs().add(orderTableRule()); + shardingRuleConfig.setDefaultDataSourceName("ds0"); + shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration()); + + Properties properties = new Properties(); + properties.setProperty("sql.show", "true"); + + return ShardingDataSourceFactory.createDataSource(dataSourceMap(), shardingRuleConfig, new ConcurrentHashMap<>(16), properties); + } + + private TableRuleConfiguration orderTableRule() { + TableRuleConfiguration tableRule = new TableRuleConfiguration(); + // 设置逻辑表名 + tableRule.setLogicTable("t_order"); + // ds${0..1}.t_order_${0..2} 也可以写成 ds$->{0..1}.t_order_$->{0..1} + tableRule.setActualDataNodes("ds${0..1}.t_order_${0..2}"); + tableRule.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_$->{order_id % 3}")); + tableRule.setKeyGenerator(customKeyGenerator()); + tableRule.setKeyGeneratorColumnName("order_id"); + return tableRule; + } + + private Map dataSourceMap() { + Map dataSourceMap = new HashMap<>(16); + + // 配置第一个数据源 + HikariDataSource ds0 = new HikariDataSource(); + ds0.setDriverClassName("com.mysql.cj.jdbc.Driver"); + ds0.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); + ds0.setUsername("root"); + ds0.setPassword("root"); + + // 配置第二个数据源 + HikariDataSource ds1 = new HikariDataSource(); + ds1.setDriverClassName("com.mysql.cj.jdbc.Driver"); + ds1.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); + ds1.setUsername("root"); + ds1.setPassword("root"); + + dataSourceMap.put("ds0", ds0); + dataSourceMap.put("ds1", ds1); + return dataSourceMap; + } + + /** + * 自定义主键生成器 + */ + private KeyGenerator customKeyGenerator() { + return new CustomSnowflakeKeyGenerator(snowflake); + } + +} diff --git a/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java new file mode 100644 index 0000000..41e218d --- /dev/null +++ b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java @@ -0,0 +1,17 @@ +package com.xkcoding.sharding.jdbc.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.sharding.jdbc.model.Order; +import org.springframework.stereotype.Component; + +/** + *

    + * 订单表 Mapper + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 13:38 + */ +@Component +public interface OrderMapper extends BaseMapper { +} diff --git a/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java new file mode 100644 index 0000000..c497ff4 --- /dev/null +++ b/demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java @@ -0,0 +1,40 @@ +package com.xkcoding.sharding.jdbc.model; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

    + * 订单表 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 13:35 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@TableName(value = "t_order") +public class Order { + /** + * 主键 + */ + private Long id; + /** + * 用户id + */ + private Long userId; + + /** + * 订单id + */ + private Long orderId; + /** + * 备注 + */ + private String remark; +} diff --git a/spring-boot-demo-sharding-jdbc/src/main/resources/application.yml b/demo-sharding-jdbc/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-sharding-jdbc/src/main/resources/application.yml rename to demo-sharding-jdbc/src/main/resources/application.yml diff --git a/demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java b/demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java new file mode 100644 index 0000000..75a4c31 --- /dev/null +++ b/demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java @@ -0,0 +1,74 @@ +package com.xkcoding.sharding.jdbc; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.xkcoding.sharding.jdbc.mapper.OrderMapper; +import com.xkcoding.sharding.jdbc.model.Order; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +/** + *

    + * 测试sharding-jdbc分库分表 + *

    + * + * @author yangkai.shen + * @date Created in 2019-03-26 13:44 + */ +@Slf4j +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoShardingJdbcApplicationTests { + @Autowired + private OrderMapper orderMapper; + + /** + * 测试新增 + */ + @Test + public void testInsert() { + for (long i = 1; i < 10; i++) { + for (long j = 1; j < 20; j++) { + Order order = Order.builder().userId(i).orderId(j).remark(RandomUtil.randomString(20)).build(); + orderMapper.insert(order); + } + } + } + + /** + * 测试更新 + */ + @Test + public void testUpdate() { + Order update = new Order(); + update.setRemark("修改备注信息"); + orderMapper.update(update, Wrappers.update().lambda().eq(Order::getOrderId, 2).eq(Order::getUserId, 2)); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + orderMapper.delete(new QueryWrapper<>()); + } + + /** + * 测试查询 + */ + @Test + public void testSelect() { + List orders = orderMapper.selectList(Wrappers.query().lambda().in(Order::getOrderId, 1, 2)); + log.info("【orders】= {}", JSONUtil.toJsonStr(orders)); + } + +} + diff --git a/spring-boot-demo-swagger-beauty/.gitignore b/demo-social/.gitignore similarity index 100% rename from spring-boot-demo-swagger-beauty/.gitignore rename to demo-social/.gitignore diff --git a/demo-social/README.md b/demo-social/README.md new file mode 100644 index 0000000..054b56b --- /dev/null +++ b/demo-social/README.md @@ -0,0 +1,495 @@ +# spring-boot-demo-social + +> 此 demo 主要演示 Spring Boot 项目如何使用 **[史上最全的第三方登录工具 - JustAuth](https://github.com/zhangyd-c/JustAuth)** 实现第三方登录,包括QQ登录、GitHub登录、微信登录、谷歌登录、微软登录、小米登录、企业微信登录。 +> +> 通过 [justauth-spring-boot-starter](https://search.maven.org/artifact/com.xkcoding/justauth-spring-boot-starter) 快速集成,好嗨哟~ +> +> JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录SDK,让登录变得**So easy!** +> +> 1. **全**:已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划! +>2. **简**:API就是奔着最简单去设计的(见后面[`快速开始`](https://github.com/zhangyd-c/JustAuth#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)),尽量让您用起来没有障碍感! +> +>PS: 本人十分幸运的参与到了这个SDK的开发,主要开发了**QQ登录、微信登录、小米登录、微软登录、谷歌登录**这 **`5`** 个第三方登录,以及一些BUG的修复工作。再次感谢 [@母狼](https://github.com/zhangyd-c) 开源这个又好用又全面的第三方登录SDK。 + +如果技术选型是 `JFinal` 的,请查看此 [**`demo`**](https://github.com/xkcoding/jfinal-justauth-demo) + +https://github.com/xkcoding/jfinal-justauth-demo + +如果技术选型是 `ActFramework` 的,请查看此 [**`demo`**](https://github.com/xkcoding/act-justauth-demo) + +https://github.com/xkcoding/act-justauth-demo + +## 1. 环境准备 + +### 1.1. 公网服务器准备 + +首先准备一台有公网IP的服务器,可以选用阿里云或者腾讯云,如果选用的是阿里云的,可以使用我的[优惠链接](https://chuangke.aliyun.com/invite?userCode=r8z5amhr)购买。 + +### 1.2. 内网穿透frp搭建 + +> frp 安装程序:https://github.com/fatedier/frp/releases + +#### 1.2.1. frp服务端搭建 + +服务端搭建在上一步准备的公网服务器上,因为服务器是centos7 x64的系统,因此,这里下载安装包版本为linux_amd64的 [frp_0.27.0_linux_amd64.tar.gz](https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz) 。 + +1. 下载安装包 + + ```shell + $ wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz + ``` + +2. 解压安装包 + + ```shell + $ tar -zxvf frp_0.27.0_linux_amd64.tar.gz + ``` + +3. 修改配置文件 + + ```shell + $ cd frp_0.27.0_linux_amd64 + $ vim frps.ini + + [common] + bind_port = 7100 + vhost_http_port = 7200 + ``` + +4. 启动frp服务端 + + ```shell + $ ./frps -c frps.ini + 2019/06/15 16:42:02 [I] [service.go:139] frps tcp listen on 0.0.0.0:7100 + 2019/06/15 16:42:02 [I] [service.go:181] http service listen on 0.0.0.0:7200 + 2019/06/15 16:42:02 [I] [root.go:204] Start frps success + ``` + +#### 1.2.2. frp客户端搭建 + +客户端搭建在本地的Mac上,因此下载安装包版本为darwin_amd64的 [frp_0.27.0_darwin_amd64.tar.gz](https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_darwin_amd64.tar.gz) 。 + +1. 下载安装包 + + ```shell + $ wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_darwin_amd64.tar.gz + ``` + +2. 解压安装包 + + ```shell + $ tar -zxvf frp_0.27.0_darwin_amd64.tar.gz + ``` + +3. 修改配置文件,配置服务端ip端口及监听的域名信息 + + ```shell + $ cd frp_0.27.0_darwin_amd64 + $ vim frpc.ini + + [common] + server_addr = 120.92.169.103 + server_port = 7100 + + [web] + type = http + local_port = 8080 + custom_domains = oauth.xkcoding.com + ``` + +4. 启动frp客户端 + + ```shell + $ ./frpc -c frpc.ini + 2019/06/15 16:48:52 [I] [service.go:221] login to server success, get run id [8bb83bae5c58afe6], server udp port [0] + 2019/06/15 16:48:52 [I] [proxy_manager.go:137] [8bb83bae5c58afe6] proxy added: [web] + 2019/06/15 16:48:52 [I] [control.go:144] [web] start proxy success + ``` + +### 1.3. 配置域名解析 + +前往阿里云DNS解析,将域名解析到我们的公网服务器上,比如我的就是将 `oauth.xkcoding.com -> 120.92.169.103` + +![image-20190615165843639](http://static.xkcoding.com/spring-boot-demo/social/063923.jpg) + +### 1.4. nginx代理 + +nginx 的搭建就不在此赘述了,只说配置 + +```nginx +server { + listen 80; + server_name oauth.xkcoding.com; + + location / { + proxy_pass http://127.0.0.1:7200; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_buffering off; + sendfile off; + proxy_max_temp_file_size 0; + client_max_body_size 10m; + client_body_buffer_size 128k; + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + proxy_temp_file_write_size 64k; + proxy_http_version 1.1; + proxy_request_buffering off; + } +} +``` + +测试配置文件是否有问题 + +```shell +$ nginx -t +nginx: the configuration file /etc/nginx/nginx.conf syntax is ok +nginx: configuration file /etc/nginx/nginx.conf test is successful +``` + +重新加载配置文件,使其生效 + +```shell +$ nginx -s reload +``` + +> 现在当我们在浏览器输入 `oauth.xkcoding.com` 的时候,网络流量其实会经历以下几个步骤: +> +> 1. 通过之前配的DNS域名解析会访问到我们的公网服务器 `120.92.169.103` 的 80 端口 +> 2. 再经过 nginx,代理到本地的 7200 端口 +> 3. 再经过 frp 穿透到我们的 Mac 电脑的 8080 端口 +> 4. 此时 8080 就是我们的应用程序端口 + +### 1.5. 第三方平台申请 + +#### 1.5.1. QQ互联平台申请 + +1. 前往 https://connect.qq.com/ +2. 申请开发者 +3. 应用管理 -> 添加网站应用,等待审核通过即可 + +![image-20190617144655429](http://static.xkcoding.com/spring-boot-demo/social/063921-1.jpg) + +#### 1.5.2. GitHub平台申请 + +1. 前往 https://github.com/settings/developers +2. 点击 `New OAuth App` 按钮创建应用 + +![image-20190617145839851](http://static.xkcoding.com/spring-boot-demo/social/063919.jpg) + +#### 1.5.3 微信开放平台申请 + +这里微信开放平台需要用企业的,个人没有资质,所以我在某宝租了一个月的资质,需要的可以 [戳我租赁](https://item.taobao.com/item.htm?spm=2013.1.w4023-5034755838.13.747a61a7ccfHwS&id=554942413474) + +> 声明:本人与该店铺无利益相关,纯属个人觉得好用做分享 +> +> 该店铺有两种方式: +> +> 1. 店铺支持帮你过企业资质,这里就用你自己的开放平台号就好了 +> 2. 临时使用可以问店家租一个月进行开发,这里租了之后,店家会把 AppID 和 AppSecret 的信息发给你,你提供回调域就好了 + +因此这里我就贴出一张授权回调的地址作参考。 + +![image-20190617153552218](http://static.xkcoding.com/spring-boot-demo/social/063927-1.jpg) + +#### 1.5.4. 谷歌开放平台申请 + +1. 前往 https://console.developers.google.com/projectcreate 创建项目 +2. 前往 https://console.developers.google.com/apis/credentials ,在第一步创建的项目下,添加应用 + +![image-20190617151119584](http://static.xkcoding.com/spring-boot-demo/social/063920.jpg) + +![image-20190617150903039](http://static.xkcoding.com/spring-boot-demo/social/063922.jpg) + +#### 1.5.5. 微软开放平台申请 + +1. 前往 https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade 注册应用 +2. 在注册应用的时候就需要填写回调地址,当然后期也可以重新修改 + +![image-20190617152529449](http://static.xkcoding.com/spring-boot-demo/social/063921.jpg) + +3. client id 在这里 + +![image-20190617152805581](http://static.xkcoding.com/spring-boot-demo/social/063927.jpg) + +4. client secret 需要自己在这里生成 + +![image-20190617152711938](http://static.xkcoding.com/spring-boot-demo/social/063924.jpg) + +#### 1.5.6. 小米开放平台申请 + +1. 申请小米开发者,审核通过 +2. 前往 https://dev.mi.com/passport/oauth2/applist 添加oauth应用,选择 `创建网页应用` +3. 填写基本信息之后,进入应用信息页面填写 `回调地址` + +![image-20190617151502414](http://static.xkcoding.com/spring-boot-demo/social/063924-1.jpg) + +4. 应用审核通过之后,可以在应用信息页面的 `应用详情` 查看到 AppKey 和 AppSecret,吐槽下,小米应用的审核速度特别慢,需要耐心等待。。。。 + +![image-20190617151624603](http://static.xkcoding.com/spring-boot-demo/social/063926.jpg) + +#### 1.5.7. 企业微信平台申请 + +> 参考:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html + +## 2. 主要代码 + +> 本 demo 采用 Redis 缓存 state,所以请准备 Redis 环境,如果没有 Redis 环境,可以将配置文件的缓存配置为 +> +> ```yaml +> justauth: +> cache: +> type: default +> ``` + +### 2.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-social + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-social + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + com.xkcoding + justauth-spring-boot-starter + ${justauth-spring-boot.version} + + + + org.projectlombok + lombok + true + + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-social + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 2.2. application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo + +spring: + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis + +justauth: + enabled: true + type: + qq: + client-id: 10******85 + client-secret: 1f7d************************d629e + redirect-uri: http://oauth.xkcoding.com/demo/oauth/qq/callback + github: + client-id: 2d25******d5f01086 + client-secret: 5a2919b************************d7871306d1 + redirect-uri: http://oauth.xkcoding.com/demo/oauth/github/callback + wechat: + client-id: wxdcb******4ff4 + client-secret: b4e9dc************************a08ed6d + redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat/callback + google: + client-id: 716******17-6db******vh******ttj320i******userco******t.com + client-secret: 9IBorn************7-E + redirect-uri: http://oauth.xkcoding.com/demo/oauth/google/callback + microsoft: + client-id: 7bdce8******************e194ad76c1b + client-secret: Iu0zZ4************************tl9PWan_. + redirect-uri: https://oauth.xkcoding.com/demo/oauth/microsoft/callback + mi: + client-id: 288************2994 + client-secret: nFeTt89************************== + redirect-uri: http://oauth.xkcoding.com/demo/oauth/mi/callback + wechat_enterprise: + client-id: ww58******f3************fbc + client-secret: 8G6PCr00j************************rgk************AyzaPc78 + redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_enterprise/callback + agent-id: 1*******2 + cache: + type: redis + prefix: 'SOCIAL::STATE::' + timeout: 1h +``` + +### 2.3. OauthController.java + +```java +/** + *

    + * 第三方登录 Controller + *

    + * + * @author yangkai.shen + * @date Created in 2019-05-17 10:07 + */ +@Slf4j +@RestController +@RequestMapping("/oauth") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class OauthController { + private final AuthRequestFactory factory; + + /** + * 登录类型 + */ + @GetMapping + public Map loginType() { + List oauthList = factory.oauthList(); + return oauthList.stream().collect(Collectors.toMap(oauth -> oauth.toLowerCase() + "登录", oauth -> "http://oauth.xkcoding.com/demo/oauth/login/" + oauth.toLowerCase())); + } + + /** + * 登录 + * + * @param oauthType 第三方登录类型 + * @param response response + * @throws IOException + */ + @RequestMapping("/login/{oauthType}") + public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { + AuthRequest authRequest = factory.get(getAuthSource(oauthType)); + response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState())); + } + + /** + * 登录成功后的回调 + * + * @param oauthType 第三方登录类型 + * @param callback 携带返回的信息 + * @return 登录成功后的信息 + */ + @RequestMapping("/{oauthType}/callback") + public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { + AuthRequest authRequest = factory.get(getAuthSource(oauthType)); + AuthResponse response = authRequest.login(callback); + log.info("【response】= {}", JSONUtil.toJsonStr(response)); + return response; + } + + private AuthSource getAuthSource(String type) { + if (StrUtil.isNotBlank(type)) { + return AuthSource.valueOf(type.toUpperCase()); + } else { + throw new RuntimeException("不支持的类型"); + } + } +} +``` + +### 2.4. 如果想要自定义 state 缓存 + +请看👉[这里](https://github.com/justauth/justauth-spring-boot-starter#2-%E7%BC%93%E5%AD%98%E9%85%8D%E7%BD%AE) + +## 3. 运行方式 + +打开浏览器,输入 http://oauth.xkcoding.com/demo/oauth ,点击各个登录方式自行测试。 + +> `Google 登录,有可能因为祖国的强大导致测试失败,自行解决~` :kissing_smiling_eyes: + +![image-20190809161032422](https://static.xkcoding.com/blog/2019-08-09-081033.png) + +## 参考 + +1. JustAuth 项目地址:https://github.com/justauth/JustAuth +2. justauth-spring-boot-starter 地址:https://github.com/justauth/justauth-spring-boot-starter +3. frp内网穿透项目地址:https://github.com/fatedier/frp +4. frp内网穿透官方中文文档:https://github.com/fatedier/frp/blob/master/README_zh.md +5. Frp实现内网穿透:https://zhuanlan.zhihu.com/p/45445979 +6. QQ互联文档:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0 +7. 微信开放平台文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN +8. GitHub第三方登录文档:https://developer.github.com/apps/building-oauth-apps/ +9. 谷歌Oauth2文档:https://developers.google.com/identity/protocols/OpenIDConnect +10. 微软Oauth2文档:https://docs.microsoft.com/zh-cn/graph/auth-v2-user +11. 小米开放平台账号服务文档:https://dev.mi.com/console/doc/detail?pId=707 + + + diff --git a/demo-social/pom.xml b/demo-social/pom.xml new file mode 100644 index 0000000..3b4a7b6 --- /dev/null +++ b/demo-social/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + demo-social + 1.0.0-SNAPSHOT + jar + + demo-social + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + com.xkcoding + justauth-spring-boot-starter + ${justauth-spring-boot.version} + + + + org.projectlombok + lombok + true + + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + + demo-social + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-social/src/main/java/com/xkcoding/social/SpringBootDemoSocialApplication.java b/demo-social/src/main/java/com/xkcoding/social/SpringBootDemoSocialApplication.java similarity index 100% rename from spring-boot-demo-social/src/main/java/com/xkcoding/social/SpringBootDemoSocialApplication.java rename to demo-social/src/main/java/com/xkcoding/social/SpringBootDemoSocialApplication.java diff --git a/demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java b/demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java new file mode 100644 index 0000000..557fd51 --- /dev/null +++ b/demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java @@ -0,0 +1,84 @@ +package com.xkcoding.social.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.justauth.AuthRequestFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

    + * 第三方登录 Controller + *

    + * + * @author yangkai.shen + * @date Created in 2019-05-17 10:07 + */ +@Slf4j +@RestController +@RequestMapping("/oauth") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class OauthController { + private final AuthRequestFactory factory; + + /** + * 登录类型 + */ + @GetMapping + public Map loginType() { + List oauthList = factory.oauthList(); + return oauthList.stream().collect(Collectors.toMap(oauth -> oauth.toLowerCase() + "登录", oauth -> "http://oauth.xkcoding.com/demo/oauth/login/" + oauth.toLowerCase())); + } + + /** + * 登录 + * + * @param oauthType 第三方登录类型 + * @param response response + * @throws IOException + */ + @RequestMapping("/login/{oauthType}") + public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { + AuthRequest authRequest = factory.get(getAuthSource(oauthType)); + response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState())); + } + + /** + * 登录成功后的回调 + * + * @param oauthType 第三方登录类型 + * @param callback 携带返回的信息 + * @return 登录成功后的信息 + */ + @RequestMapping("/{oauthType}/callback") + public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { + AuthRequest authRequest = factory.get(getAuthSource(oauthType)); + AuthResponse response = authRequest.login(callback); + log.info("【response】= {}", JSONUtil.toJsonStr(response)); + return response; + } + + private AuthSource getAuthSource(String type) { + if (StrUtil.isNotBlank(type)) { + return AuthSource.valueOf(type.toUpperCase()); + } else { + throw new RuntimeException("不支持的类型"); + } + } +} diff --git a/spring-boot-demo-social/src/main/resources/application.yml b/demo-social/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-social/src/main/resources/application.yml rename to demo-social/src/main/resources/application.yml diff --git a/spring-boot-demo-social/src/test/java/com/xkcoding/social/SpringBootDemoSocialApplicationTests.java b/demo-social/src/test/java/com/xkcoding/social/SpringBootDemoSocialApplicationTests.java similarity index 100% rename from spring-boot-demo-social/src/test/java/com/xkcoding/social/SpringBootDemoSocialApplicationTests.java rename to demo-social/src/test/java/com/xkcoding/social/SpringBootDemoSocialApplicationTests.java diff --git a/spring-boot-demo-swagger/.gitignore b/demo-swagger-beauty/.gitignore similarity index 100% rename from spring-boot-demo-swagger/.gitignore rename to demo-swagger-beauty/.gitignore diff --git a/demo-swagger-beauty/README.md b/demo-swagger-beauty/README.md new file mode 100644 index 0000000..b488af5 --- /dev/null +++ b/demo-swagger-beauty/README.md @@ -0,0 +1,282 @@ +# spring-boot-demo-swagger-beauty + +> 此 demo 主要演示如何集成第三方的 swagger 来替换原生的 swagger,美化文档样式。本 demo 使用 [swagger-spring-boot-starter](https://github.com/battcn/swagger-spring-boot) 集成。 +> +> 启动项目,访问地址:http://localhost:8080/demo/swagger-ui.html#/ +> +> 用户名:xkcoding +> +> 密码:123456 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-swagger-beauty + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-swagger-beauty + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.2-RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.battcn + swagger-spring-boot-starter + ${battcn.swagger.version} + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-swagger-beauty + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + swagger: + enabled: true + title: spring-boot-demo + description: 这是一个简单的 Swagger API 演示 + version: 1.0.0-SNAPSHOT + contact: + name: Yangkai.Shen + email: 237497819@qq.com + url: http://xkcoding.com + # swagger扫描的基础包,默认:全扫描 + # base-package: + # 需要处理的基础URL规则,默认:/** + # base-path: + # 需要排除的URL规则,默认:空 + # exclude-path: + security: + # 是否启用 swagger 登录验证 + filter-plugin: true + username: xkcoding + password: 123456 + global-response-messages: + GET[0]: + code: 400 + message: Bad Request,一般为请求参数不对 + GET[1]: + code: 404 + message: NOT FOUND,一般为请求路径不对 + GET[2]: + code: 500 + message: ERROR,一般为程序内部错误 + POST[0]: + code: 400 + message: Bad Request,一般为请求参数不对 + POST[1]: + code: 404 + message: NOT FOUND,一般为请求路径不对 + POST[2]: + code: 500 + message: ERROR,一般为程序内部错误 +``` + +## ApiResponse.java + +```java +/** + *

    + * 通用API接口返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "通用PI接口返回", description = "Common Api Response") +public class ApiResponse implements Serializable { + private static final long serialVersionUID = -8987146499044811408L; + /** + * 通用返回状态 + */ + @ApiModelProperty(value = "通用返回状态", required = true) + private Integer code; + /** + * 通用返回信息 + */ + @ApiModelProperty(value = "通用返回信息", required = true) + private String message; + /** + * 通用返回数据 + */ + @ApiModelProperty(value = "通用返回数据", required = true) + private T data; +} +``` + +## User.java + +```java +/** + *

    + * 用户实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "用户实体", description = "User Entity") +public class User implements Serializable { + private static final long serialVersionUID = 5057954049311281252L; + /** + * 主键id + */ + @ApiModelProperty(value = "主键id", required = true) + private Integer id; + /** + * 用户名 + */ + @ApiModelProperty(value = "用户名", required = true) + private String name; + /** + * 工作岗位 + */ + @ApiModelProperty(value = "工作岗位", required = true) + private String job; +} +``` + +## UserController.java + +```java +/** + *

    + * User Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:25 + */ +@RestController +@RequestMapping("/user") +@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") +@Slf4j +public class UserController { + @GetMapping + @ApiOperation(value = "条件查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) + public ApiResponse getByUserName(String username) { + log.info("多个参数用 @ApiImplicitParams"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(1, username, "JAVA")).build(); + } + + @GetMapping("/{id}") + @ApiOperation(value = "主键查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) + public ApiResponse get(@PathVariable Integer id) { + log.info("单个参数用 @ApiImplicitParam"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(id, "u1", "p1")).build(); + } + + @DeleteMapping("/{id}") + @ApiOperation(value = "删除用户(DONE)", notes = "备注") + @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) + public void delete(@PathVariable Integer id) { + log.info("单个参数用 ApiImplicitParam"); + } + + @PostMapping + @ApiOperation(value = "添加用户(DONE)") + public User post(@RequestBody User user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PostMapping("/multipar") + @ApiOperation(value = "添加用户(DONE)") + public List multipar(@RequestBody List user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + + return user; + } + + @PostMapping("/array") + @ApiOperation(value = "添加用户(DONE)") + public User[] array(@RequestBody User[] user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PutMapping("/{id}") + @ApiOperation(value = "修改用户(DONE)") + public void put(@PathVariable Long id, @RequestBody User user) { + log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); + } + + @PostMapping("/{id}/file") + @ApiOperation(value = "文件上传(DONE)") + public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { + log.info(file.getContentType()); + log.info(file.getName()); + log.info(file.getOriginalFilename()); + return file.getOriginalFilename(); + } +} +``` + +## 参考 + +- https://github.com/battcn/swagger-spring-boot/blob/master/README.md +- 几款比较好看的swagger-ui,具体使用方法参见各个依赖的官方文档: + - [battcn](https://github.com/battcn) 的 [swagger-spring-boot-starter](https://github.com/battcn/swagger-spring-boot) 文档:https://github.com/battcn/swagger-spring-boot/blob/master/README.md + - [ swagger-ui-layer](https://gitee.com/caspar-chen/Swagger-UI-layer) 文档:https://gitee.com/caspar-chen/Swagger-UI-layer#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8 + - [swagger-bootstrap-ui](https://gitee.com/xiaoym/swagger-bootstrap-ui) 文档:https://gitee.com/xiaoym/swagger-bootstrap-ui#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E + - [swagger-ui-themes](https://github.com/ostranme/swagger-ui-themes) 文档:https://github.com/ostranme/swagger-ui-themes#getting-started diff --git a/demo-swagger-beauty/pom.xml b/demo-swagger-beauty/pom.xml new file mode 100644 index 0000000..80704d2 --- /dev/null +++ b/demo-swagger-beauty/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + demo-swagger-beauty + 1.0.0-SNAPSHOT + jar + + demo-swagger-beauty + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.2-RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.battcn + swagger-spring-boot-starter + ${battcn.swagger.version} + + + + org.projectlombok + lombok + true + + + + + demo-swagger-beauty + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java new file mode 100644 index 0000000..349509d --- /dev/null +++ b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.swagger.beauty; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 11:18 + */ +@SpringBootApplication +public class SpringBootDemoSwaggerBeautyApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoSwaggerBeautyApplication.class, args); + } +} diff --git a/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java new file mode 100644 index 0000000..f7ae5bd --- /dev/null +++ b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java @@ -0,0 +1,42 @@ +package com.xkcoding.swagger.beauty.common; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * 通用API接口返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "通用PI接口返回", description = "Common Api Response") +public class ApiResponse implements Serializable { + private static final long serialVersionUID = -8987146499044811408L; + /** + * 通用返回状态 + */ + @ApiModelProperty(value = "通用返回状态", required = true) + private Integer code; + /** + * 通用返回信息 + */ + @ApiModelProperty(value = "通用返回信息", required = true) + private String message; + /** + * 通用返回数据 + */ + @ApiModelProperty(value = "通用返回数据", required = true) + private T data; +} diff --git a/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java new file mode 100644 index 0000000..9fbb39d --- /dev/null +++ b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java @@ -0,0 +1,89 @@ +package com.xkcoding.swagger.beauty.controller; + +import com.battcn.boot.swagger.model.DataType; +import com.battcn.boot.swagger.model.ParamType; +import com.xkcoding.swagger.beauty.common.ApiResponse; +import com.xkcoding.swagger.beauty.entity.User; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + *

    + * User Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:25 + */ +@RestController +@RequestMapping("/user") +@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") +@Slf4j +public class UserController { + @GetMapping + @ApiOperation(value = "条件查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) + public ApiResponse getByUserName(String username) { + log.info("多个参数用 @ApiImplicitParams"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(1, username, "JAVA")).build(); + } + + @GetMapping("/{id}") + @ApiOperation(value = "主键查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) + public ApiResponse get(@PathVariable Integer id) { + log.info("单个参数用 @ApiImplicitParam"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(id, "u1", "p1")).build(); + } + + @DeleteMapping("/{id}") + @ApiOperation(value = "删除用户(DONE)", notes = "备注") + @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) + public void delete(@PathVariable Integer id) { + log.info("单个参数用 ApiImplicitParam"); + } + + @PostMapping + @ApiOperation(value = "添加用户(DONE)") + public User post(@RequestBody User user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PostMapping("/multipar") + @ApiOperation(value = "添加用户(DONE)") + public List multipar(@RequestBody List user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + + return user; + } + + @PostMapping("/array") + @ApiOperation(value = "添加用户(DONE)") + public User[] array(@RequestBody User[] user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PutMapping("/{id}") + @ApiOperation(value = "修改用户(DONE)") + public void put(@PathVariable Long id, @RequestBody User user) { + log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); + } + + @PostMapping("/{id}/file") + @ApiOperation(value = "文件上传(DONE)") + public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { + log.info(file.getContentType()); + log.info(file.getName()); + log.info(file.getOriginalFilename()); + return file.getOriginalFilename(); + } +} diff --git a/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java new file mode 100644 index 0000000..3a75323 --- /dev/null +++ b/demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java @@ -0,0 +1,40 @@ +package com.xkcoding.swagger.beauty.entity; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * 用户实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-28 14:13 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "用户实体", description = "User Entity") +public class User implements Serializable { + private static final long serialVersionUID = 5057954049311281252L; + /** + * 主键id + */ + @ApiModelProperty(value = "主键id", required = true) + private Integer id; + /** + * 用户名 + */ + @ApiModelProperty(value = "用户名", required = true) + private String name; + /** + * 工作岗位 + */ + @ApiModelProperty(value = "工作岗位", required = true) + private String job; +} diff --git a/spring-boot-demo-swagger-beauty/src/main/resources/application.yml b/demo-swagger-beauty/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-swagger-beauty/src/main/resources/application.yml rename to demo-swagger-beauty/src/main/resources/application.yml diff --git a/spring-boot-demo-swagger-beauty/src/test/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplicationTests.java b/demo-swagger-beauty/src/test/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplicationTests.java similarity index 100% rename from spring-boot-demo-swagger-beauty/src/test/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplicationTests.java rename to demo-swagger-beauty/src/test/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplicationTests.java diff --git a/spring-boot-demo-task-quartz/.gitignore b/demo-swagger/.gitignore similarity index 100% rename from spring-boot-demo-task-quartz/.gitignore rename to demo-swagger/.gitignore diff --git a/demo-swagger/README.md b/demo-swagger/README.md new file mode 100644 index 0000000..77f3bd6 --- /dev/null +++ b/demo-swagger/README.md @@ -0,0 +1,244 @@ +# spring-boot-demo-swagger + +> 此 demo 主要演示了 Spring Boot 如何集成原生 swagger ,自动生成 API 文档。 +> +> 启动项目,访问地址:http://localhost:8080/demo/swagger-ui.html#/ + +# pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-swagger + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-swagger + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.9.2 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-swagger + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## Swagger2Config.java + +```java +/** + *

    + * Swagger2 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:14 + */ +@Configuration +@EnableSwagger2 +public class Swagger2Config { + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("com.xkcoding.swagger.controller")) + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("spring-boot-demo") + .description("这是一个简单的 Swagger API 演示") + .contact(new Contact("Yangkai.Shen", "http://xkcoding.com", "237497819@qq.com")) + .version("1.0.0-SNAPSHOT") + .build(); + } + +} +``` + +## UserController.java + +> 主要演示API层的注解。 + +```java +/** + *

    + * User Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:30 + */ +@RestController +@RequestMapping("/user") +@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") +@Slf4j +public class UserController { + @GetMapping + @ApiOperation(value = "条件查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) + public ApiResponse getByUserName(String username) { + log.info("多个参数用 @ApiImplicitParams"); + return ApiResponse.builder().code(200) + .message("操作成功") + .data(new User(1, username, "JAVA")) + .build(); + } + + @GetMapping("/{id}") + @ApiOperation(value = "主键查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) + public ApiResponse get(@PathVariable Integer id) { + log.info("单个参数用 @ApiImplicitParam"); + return ApiResponse.builder().code(200) + .message("操作成功") + .data(new User(id, "u1", "p1")) + .build(); + } + + @DeleteMapping("/{id}") + @ApiOperation(value = "删除用户(DONE)", notes = "备注") + @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) + public void delete(@PathVariable Integer id) { + log.info("单个参数用 ApiImplicitParam"); + } + + @PostMapping + @ApiOperation(value = "添加用户(DONE)") + public User post(@RequestBody User user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PostMapping("/multipar") + @ApiOperation(value = "添加用户(DONE)") + public List multipar(@RequestBody List user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + + return user; + } + + @PostMapping("/array") + @ApiOperation(value = "添加用户(DONE)") + public User[] array(@RequestBody User[] user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PutMapping("/{id}") + @ApiOperation(value = "修改用户(DONE)") + public void put(@PathVariable Long id, @RequestBody User user) { + log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); + } + + @PostMapping("/{id}/file") + @ApiOperation(value = "文件上传(DONE)") + public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { + log.info(file.getContentType()); + log.info(file.getName()); + log.info(file.getOriginalFilename()); + return file.getOriginalFilename(); + } +} +``` + +## ApiResponse.java + +> 主要演示了 实体类 的注解。 + +```java +/** + *

    + * 通用API接口返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:30 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "通用PI接口返回", description = "Common Api Response") +public class ApiResponse implements Serializable { + private static final long serialVersionUID = -8987146499044811408L; + /** + * 通用返回状态 + */ + @ApiModelProperty(value = "通用返回状态", required = true) + private Integer code; + /** + * 通用返回信息 + */ + @ApiModelProperty(value = "通用返回信息", required = true) + private String message; + /** + * 通用返回数据 + */ + @ApiModelProperty(value = "通用返回数据", required = true) + private T data; +} +``` + +## 参考 + +1. swagger 官方网站:https://swagger.io/ + +2. swagger 官方文档:https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Getting-started + +3. swagger 常用注解:https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations diff --git a/demo-swagger/pom.xml b/demo-swagger/pom.xml new file mode 100644 index 0000000..69e21d8 --- /dev/null +++ b/demo-swagger/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + demo-swagger + 1.0.0-SNAPSHOT + jar + + demo-swagger + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.9.2 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + + org.projectlombok + lombok + true + + + + + demo-swagger + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java b/demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java new file mode 100644 index 0000000..1f9dbae --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.swagger; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 13:25 + */ +@SpringBootApplication +public class SpringBootDemoSwaggerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoSwaggerApplication.class, args); + } +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java b/demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java new file mode 100644 index 0000000..fb726cd --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java @@ -0,0 +1,42 @@ +package com.xkcoding.swagger.common; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * 通用API接口返回 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:30 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "通用PI接口返回", description = "Common Api Response") +public class ApiResponse implements Serializable { + private static final long serialVersionUID = -8987146499044811408L; + /** + * 通用返回状态 + */ + @ApiModelProperty(value = "通用返回状态", required = true) + private Integer code; + /** + * 通用返回信息 + */ + @ApiModelProperty(value = "通用返回信息", required = true) + private String message; + /** + * 通用返回数据 + */ + @ApiModelProperty(value = "通用返回数据", required = true) + private T data; +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java b/demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java new file mode 100644 index 0000000..b5266a6 --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java @@ -0,0 +1,25 @@ +package com.xkcoding.swagger.common; + +/** + *

    + * 方便在 @ApiImplicitParam 的 dataType 属性使用 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 13:23 + */ +public final class DataType { + + public final static String STRING = "String"; + public final static String INT = "int"; + public final static String LONG = "long"; + public final static String DOUBLE = "double"; + public final static String FLOAT = "float"; + public final static String BYTE = "byte"; + public final static String BOOLEAN = "boolean"; + public final static String ARRAY = "array"; + public final static String BINARY = "binary"; + public final static String DATETIME = "dateTime"; + public final static String PASSWORD = "password"; + +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java b/demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java new file mode 100644 index 0000000..775ef6f --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java @@ -0,0 +1,19 @@ +package com.xkcoding.swagger.common; + +/** + *

    + * 方便在 @ApiImplicitParam 的 paramType 属性使用 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 13:24 + */ +public final class ParamType { + + public final static String QUERY = "query"; + public final static String HEADER = "header"; + public final static String PATH = "path"; + public final static String BODY = "body"; + public final static String FORM = "form"; + +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java b/demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java new file mode 100644 index 0000000..6bc94ff --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java @@ -0,0 +1,35 @@ +package com.xkcoding.swagger.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + *

    + * Swagger2 配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:14 + */ +@Configuration +@EnableSwagger2 +public class Swagger2Config { + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.xkcoding.swagger.controller")).paths(PathSelectors.any()).build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("spring-boot-demo").description("这是一个简单的 Swagger API 演示").contact(new Contact("Yangkai.Shen", "http://xkcoding.com", "237497819@qq.com")).version("1.0.0-SNAPSHOT").build(); + } + +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java b/demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java new file mode 100644 index 0000000..369f6f7 --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java @@ -0,0 +1,89 @@ +package com.xkcoding.swagger.controller; + +import com.xkcoding.swagger.common.ApiResponse; +import com.xkcoding.swagger.common.DataType; +import com.xkcoding.swagger.common.ParamType; +import com.xkcoding.swagger.entity.User; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + *

    + * User Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:30 + */ +@RestController +@RequestMapping("/user") +@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") +@Slf4j +public class UserController { + @GetMapping + @ApiOperation(value = "条件查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) + public ApiResponse getByUserName(String username) { + log.info("多个参数用 @ApiImplicitParams"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(1, username, "JAVA")).build(); + } + + @GetMapping("/{id}") + @ApiOperation(value = "主键查询(DONE)", notes = "备注") + @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) + public ApiResponse get(@PathVariable Integer id) { + log.info("单个参数用 @ApiImplicitParam"); + return ApiResponse.builder().code(200).message("操作成功").data(new User(id, "u1", "p1")).build(); + } + + @DeleteMapping("/{id}") + @ApiOperation(value = "删除用户(DONE)", notes = "备注") + @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) + public void delete(@PathVariable Integer id) { + log.info("单个参数用 ApiImplicitParam"); + } + + @PostMapping + @ApiOperation(value = "添加用户(DONE)") + public User post(@RequestBody User user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PostMapping("/multipar") + @ApiOperation(value = "添加用户(DONE)") + public List multipar(@RequestBody List user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + + return user; + } + + @PostMapping("/array") + @ApiOperation(value = "添加用户(DONE)") + public User[] array(@RequestBody User[] user) { + log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); + return user; + } + + @PutMapping("/{id}") + @ApiOperation(value = "修改用户(DONE)") + public void put(@PathVariable Long id, @RequestBody User user) { + log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); + } + + @PostMapping("/{id}/file") + @ApiOperation(value = "文件上传(DONE)") + public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { + log.info(file.getContentType()); + log.info(file.getName()); + log.info(file.getOriginalFilename()); + return file.getOriginalFilename(); + } +} diff --git a/demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java b/demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java new file mode 100644 index 0000000..a89baea --- /dev/null +++ b/demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java @@ -0,0 +1,40 @@ +package com.xkcoding.swagger.entity; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

    + * 用户实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-29 11:31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "用户实体", description = "User Entity") +public class User implements Serializable { + private static final long serialVersionUID = 5057954049311281252L; + /** + * 主键id + */ + @ApiModelProperty(value = "主键id", required = true) + private Integer id; + /** + * 用户名 + */ + @ApiModelProperty(value = "用户名", required = true) + private String name; + /** + * 工作岗位 + */ + @ApiModelProperty(value = "工作岗位", required = true) + private String job; +} diff --git a/spring-boot-demo-template-beetl/src/main/resources/application.yml b/demo-swagger/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-template-beetl/src/main/resources/application.yml rename to demo-swagger/src/main/resources/application.yml diff --git a/spring-boot-demo-swagger/src/test/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplicationTests.java b/demo-swagger/src/test/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplicationTests.java similarity index 100% rename from spring-boot-demo-swagger/src/test/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplicationTests.java rename to demo-swagger/src/test/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplicationTests.java diff --git a/spring-boot-demo-task-xxl-job/.gitignore b/demo-task-quartz/.gitignore similarity index 100% rename from spring-boot-demo-task-xxl-job/.gitignore rename to demo-task-quartz/.gitignore diff --git a/spring-boot-demo-task-quartz/README.md b/demo-task-quartz/README.md similarity index 100% rename from spring-boot-demo-task-quartz/README.md rename to demo-task-quartz/README.md diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_cloudscape.sql b/demo-task-quartz/init/dbTables/tables_cloudscape.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_cloudscape.sql rename to demo-task-quartz/init/dbTables/tables_cloudscape.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_cubrid.sql b/demo-task-quartz/init/dbTables/tables_cubrid.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_cubrid.sql rename to demo-task-quartz/init/dbTables/tables_cubrid.sql diff --git a/demo-task-quartz/init/dbTables/tables_db2.sql b/demo-task-quartz/init/dbTables/tables_db2.sql new file mode 100644 index 0000000..a8ebabd --- /dev/null +++ b/demo-task-quartz/init/dbTables/tables_db2.sql @@ -0,0 +1,148 @@ +# +# Thanks to Horia Muntean for submitting this.... +# +# .. known to work with DB2 7.1 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" +# .. likely to work with others... +# +# In your Quartz properties file, you'll need to set +# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate +# +# If you're using DB2 6.x you'll want to set this property to +# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate +# +# Note that the blob column size (e.g. blob(2000)) dictates theount of data that can be stored in +# that blob - i.e. limits theount of data you can put into your JobDataMap +# + + +create table qrtz_job_details ( + sched_name varchar(120) not null, + job_name varchar(80) not null, + job_group varchar(80) not null, + description varchar(120) null, + job_class_name varchar(128) not null, + is_durable varchar(1) not null, + is_nonconcurrent varchar(1) not null, + is_update_data varchar(1) not null, + requests_recovery varchar(1) not null, + job_data blob(2000), + primary key (sched_name,job_name,job_group) +) + +create table qrtz_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + job_name varchar(80) not null, + job_group varchar(80) not null, + description varchar(120) null, + next_fire_time bigint, + prev_fire_time bigint, + priority integer, + trigger_state varchar(16) not null, + trigger_type varchar(8) not null, + start_time bigint not null, + end_time bigint, + calendar_name varchar(80), + misfire_instr smallint, + job_data blob(2000), + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) +) + +create table qrtz_simple_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + repeat_count bigint not null, + repeat_interval bigint not null, + times_triggered bigint not null, + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +) + +create table qrtz_cron_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + cron_expression varchar(120) not null, + time_zone_id varchar(80), + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +) + +CREATE TABLE qrtz_simprop_triggers + ( + sched_name varchar(120) not null, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 VARCHAR(1) NULL, + BOOL_PROP_2 VARCHAR(1) NULL, + PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) +) + +create table qrtz_blob_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + blob_data blob(2000) null, + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +) + +create table qrtz_calendars( + sched_name varchar(120) not null, + calendar_name varchar(80) not null, + calendar blob(2000) not null, + primary key (sched_name,calendar_name) +) + +create table qrtz_fired_triggers( + sched_name varchar(120) not null, + entry_id varchar(95) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + instance_name varchar(80) not null, + fired_time bigint not null, + sched_time bigint not null, + priority integer not null, + state varchar(16) not null, + job_name varchar(80) null, + job_group varchar(80) null, + is_nonconcurrent varchar(1) null, + requests_recovery varchar(1) null, + primary key (sched_name,entry_id) +); + + +create table qrtz_paused_trigger_grps( + sched_name varchar(120) not null, + trigger_group varchar(80) not null, + primary key (sched_name,trigger_group) +); + +create table qrtz_scheduler_state ( + sched_name varchar(120) not null, + instance_name varchar(80) not null, + last_checkin_time bigint not null, + checkin_interval bigint not null, + primary key (sched_name,instance_name) +); + +create table qrtz_locks + ( + sched_name varchar(120) not null, + lock_name varchar(40) not null, + primary key (sched_name,lock_name) +); diff --git a/demo-task-quartz/init/dbTables/tables_db2_v72.sql b/demo-task-quartz/init/dbTables/tables_db2_v72.sql new file mode 100644 index 0000000..91e6d40 --- /dev/null +++ b/demo-task-quartz/init/dbTables/tables_db2_v72.sql @@ -0,0 +1,163 @@ +-- +-- Thanks to Horia Muntean for submitting this, Mikkel Heisterberg for updating it +-- +-- .. known to work with DB2 7.2 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" +-- .. likely to work with others... +-- +-- In your Quartz properties file, you'll need to set +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v7Delegate +-- +-- or +-- +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate +-- +-- If you're using DB2 6.x you'll want to set this property to +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate +-- +-- Note that the blob column size (e.g. blob(2000)) dictates theount of data that can be stored in +-- that blob - i.e. limits theount of data you can put into your JobDataMap +-- + +DROP TABLE QRTZ_FIRED_TRIGGERS; +DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE QRTZ_SCHEDULER_STATE; +DROP TABLE QRTZ_LOCKS; +DROP TABLE QRTZ_SIMPLE_TRIGGERS; +DROP TABLE QRTZ_SIMPROP_TRIGGERS; +DROP TABLE QRTZ_CRON_TRIGGERS; +DROP TABLE QRTZ_TRIGGERS; +DROP TABLE QRTZ_JOB_DETAILS; +DROP TABLE QRTZ_CALENDARS; +DROP TABLE QRTZ_BLOB_TRIGGERS; + +create table qrtz_job_details ( + sched_name varchar(120) not null, + job_name varchar(80) not null, + job_group varchar(80) not null, + description varchar(120), + job_class_name varchar(128) not null, + is_durable varchar(1) not null, + is_nonconcurrent varchar(1) not null, + is_update_data varchar(1) not null, + requests_recovery varchar(1) not null, + job_data blob(2000), + primary key (sched_name,job_name,job_group) +); + +create table qrtz_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + job_name varchar(80) not null, + job_group varchar(80) not null, + description varchar(120), + next_fire_time bigint, + prev_fire_time bigint, + priority integer, + trigger_state varchar(16) not null, + trigger_type varchar(8) not null, + start_time bigint not null, + end_time bigint, + calendar_name varchar(80), + misfire_instr smallint, + job_data blob(2000), + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) +); + +create table qrtz_simple_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + repeat_count bigint not null, + repeat_interval bigint not null, + times_triggered bigint not null, + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +); + +create table qrtz_cron_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + cron_expression varchar(120) not null, + time_zone_id varchar(80), + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +); + +CREATE TABLE qrtz_simprop_triggers + ( + sched_name varchar(120) not null, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 VARCHAR(1) NULL, + BOOL_PROP_2 VARCHAR(1) NULL, + PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) +); + +create table qrtz_blob_triggers( + sched_name varchar(120) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + blob_data blob(2000), + primary key (sched_name,trigger_name,trigger_group), + foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) +); + +create table qrtz_calendars( + sched_name varchar(120) not null, + calendar_name varchar(80) not null, + calendar blob(2000) not null, + primary key (sched_name,calendar_name) +); + +create table qrtz_fired_triggers( + sched_name varchar(120) not null, + entry_id varchar(95) not null, + trigger_name varchar(80) not null, + trigger_group varchar(80) not null, + instance_name varchar(80) not null, + fired_time bigint not null, + sched_time bigint not null, + priority integer not null, + state varchar(16) not null, + job_name varchar(80), + job_group varchar(80), + is_nonconcurrent varchar(1), + requests_recovery varchar(1), + primary key (sched_name,entry_id) +); + + +create table qrtz_paused_trigger_grps( + sched_name varchar(120) not null, + trigger_group varchar(80) not null, + primary key (sched_name,trigger_group) +); + +create table qrtz_scheduler_state ( + sched_name varchar(120) not null, + instance_name varchar(80) not null, + last_checkin_time bigint not null, + checkin_interval bigint not null, + primary key (sched_name,instance_name) +); + +create table qrtz_locks + ( + sched_name varchar(120) not null, + lock_name varchar(40) not null, + primary key (sched_name,lock_name) +); diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_db2_v8.sql b/demo-task-quartz/init/dbTables/tables_db2_v8.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_db2_v8.sql rename to demo-task-quartz/init/dbTables/tables_db2_v8.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_db2_v95.sql b/demo-task-quartz/init/dbTables/tables_db2_v95.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_db2_v95.sql rename to demo-task-quartz/init/dbTables/tables_db2_v95.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_derby.sql b/demo-task-quartz/init/dbTables/tables_derby.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_derby.sql rename to demo-task-quartz/init/dbTables/tables_derby.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_derby_previous.sql b/demo-task-quartz/init/dbTables/tables_derby_previous.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_derby_previous.sql rename to demo-task-quartz/init/dbTables/tables_derby_previous.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_firebird.sql b/demo-task-quartz/init/dbTables/tables_firebird.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_firebird.sql rename to demo-task-quartz/init/dbTables/tables_firebird.sql diff --git a/demo-task-quartz/init/dbTables/tables_h2.sql b/demo-task-quartz/init/dbTables/tables_h2.sql new file mode 100644 index 0000000..8e14289 --- /dev/null +++ b/demo-task-quartz/init/dbTables/tables_h2.sql @@ -0,0 +1,249 @@ +-- Thanks toir Kibbar and Peter Rietzler for contributing the schema for H2 database, +-- and verifying that it works with Quartz's StdJDBCDelegate +-- +-- Note, Quartz depends on row-level locking which means you must use the MVCC=TRUE +-- setting on your H2 database, or you will experience dead-locks +-- +-- +-- In your Quartz properties file, you'll need to set +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate + +CREATE TABLE QRTZ_CALENDARS ( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR (200) NOT NULL , + CALENDAR IMAGE NOT NULL +); + +CREATE TABLE QRTZ_CRON_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + CRON_EXPRESSION VARCHAR (120) NOT NULL , + TIME_ZONE_ID VARCHAR (80) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR (95) NOT NULL , + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + INSTANCE_NAME VARCHAR (200) NOT NULL , + FIRED_TIME BIGINT NOT NULL , + SCHED_TIME BIGINT NOT NULL , + PRIORITY INTEGER NOT NULL , + STATE VARCHAR (16) NOT NULL, + JOB_NAME VARCHAR (200) NULL , + JOB_GROUP VARCHAR (200) NULL , + IS_NONCONCURRENT BOOLEAN NULL , + REQUESTS_RECOVERY BOOLEAN NULL +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR (200) NOT NULL +); + +CREATE TABLE QRTZ_SCHEDULER_STATE ( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR (200) NOT NULL , + LAST_CHECKIN_TIME BIGINT NOT NULL , + CHECKIN_INTERVAL BIGINT NOT NULL +); + +CREATE TABLE QRTZ_LOCKS ( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR (40) NOT NULL +); + +CREATE TABLE QRTZ_JOB_DETAILS ( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + JOB_CLASS_NAME VARCHAR (250) NOT NULL , + IS_DURABLE BOOLEAN NOT NULL , + IS_NONCONCURRENT BOOLEAN NOT NULL , + IS_UPDATE_DATA BOOLEAN NOT NULL , + REQUESTS_RECOVERY BOOLEAN NOT NULL , + JOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + REPEAT_COUNT BIGINT NOT NULL , + REPEAT_INTERVAL BIGINT NOT NULL , + TIMES_TRIGGERED BIGINT NOT NULL +); + +CREATE TABLE qrtz_simprop_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INTEGER NULL, + INT_PROP_2 INTEGER NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL, +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + BLOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + NEXT_FIRE_TIME BIGINT NULL , + PREV_FIRE_TIME BIGINT NULL , + PRIORITY INTEGER NULL , + TRIGGER_STATE VARCHAR (16) NOT NULL , + TRIGGER_TYPE VARCHAR (8) NOT NULL , + START_TIME BIGINT NOT NULL , + END_TIME BIGINT NULL , + CALENDAR_NAME VARCHAR (200) NULL , + MISFIRE_INSTR SMALLINT NULL , + JOB_DATA IMAGE NULL +); + +ALTER TABLE QRTZ_CALENDARS ADD + CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY + ( + SCHED_NAME, + CALENDAR_NAME + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_FIRED_TRIGGERS ADD + CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + ENTRY_ID + ); + +ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD + CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SCHEDULER_STATE ADD + CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY + ( + SCHED_NAME, + INSTANCE_NAME + ); + +ALTER TABLE QRTZ_LOCKS ADD + CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY + ( + SCHED_NAME, + LOCK_NAME + ); + +ALTER TABLE QRTZ_JOB_DETAILS ADD + CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ) REFERENCES QRTZ_JOB_DETAILS ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +COMMIT; diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_hsqldb.sql b/demo-task-quartz/init/dbTables/tables_hsqldb.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_hsqldb.sql rename to demo-task-quartz/init/dbTables/tables_hsqldb.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_hsqldb_old.sql b/demo-task-quartz/init/dbTables/tables_hsqldb_old.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_hsqldb_old.sql rename to demo-task-quartz/init/dbTables/tables_hsqldb_old.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_informix.sql b/demo-task-quartz/init/dbTables/tables_informix.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_informix.sql rename to demo-task-quartz/init/dbTables/tables_informix.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_mysql.sql b/demo-task-quartz/init/dbTables/tables_mysql.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_mysql.sql rename to demo-task-quartz/init/dbTables/tables_mysql.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_mysql_innodb.sql b/demo-task-quartz/init/dbTables/tables_mysql_innodb.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_mysql_innodb.sql rename to demo-task-quartz/init/dbTables/tables_mysql_innodb.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_oracle.sql b/demo-task-quartz/init/dbTables/tables_oracle.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_oracle.sql rename to demo-task-quartz/init/dbTables/tables_oracle.sql diff --git a/demo-task-quartz/init/dbTables/tables_pointbase.sql b/demo-task-quartz/init/dbTables/tables_pointbase.sql new file mode 100644 index 0000000..b4a9929 --- /dev/null +++ b/demo-task-quartz/init/dbTables/tables_pointbase.sql @@ -0,0 +1,180 @@ +# +# Thanks to Gregg Freeman +# +# +# ...you may want to change defined the size of the "blob" columns before +# creating the tables (particularly for the qrtz_job_details.job_data column), +# if you will be storing largeounts of data in them +# +# +delete from qrtz_fired_triggers; +delete from qrtz_simple_triggers; +delete from qrtz_simprop_triggers; +delete from qrtz_cron_triggers; +delete from qrtz_blob_triggers; +delete from qrtz_triggers; +delete from qrtz_job_details; +delete from qrtz_calendars; +delete from qrtz_paused_trigger_grps; +delete from qrtz_locks; +delete from qrtz_scheduler_state; + +drop table qrtz_calendars; +drop table qrtz_fired_triggers; +drop table qrtz_blob_triggers; +drop table qrtz_cron_triggers; +drop table qrtz_simple_triggers; +drop table qrtz_simprop_triggers; +drop table qrtz_triggers; +drop table qrtz_job_details; +drop table qrtz_paused_trigger_grps; +drop table qrtz_locks; +drop table qrtz_scheduler_state; + + +CREATE TABLE qrtz_job_details + ( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR2(80) NOT NULL, + JOB_GROUP VARCHAR2(80) NOT NULL, + DESCRIPTION VARCHAR2(120) NULL, + JOB_CLASS_NAME VARCHAR2(128) NOT NULL, + IS_DURABLE BOOLEAN NOT NULL, + IS_NONCONCURRENT BOOLEAN NOT NULL, + IS_UPDATE_DATA BOOLEAN NOT NULL, + REQUESTS_RECOVERY BOOLEAN NOT NULL, + JOB_DATA BLOB(4K) NULL, + PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +); + +CREATE TABLE qrtz_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR2(80) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + JOB_NAME VARCHAR2(80) NOT NULL, + JOB_GROUP VARCHAR2(80) NOT NULL, + DESCRIPTION VARCHAR2(120) NULL, + NEXT_FIRE_TIME NUMBER(13) NULL, + PREV_FIRE_TIME NUMBER(13) NULL, + PRIORITY NUMBER(13) NULL, + TRIGGER_STATE VARCHAR2(16) NOT NULL, + TRIGGER_TYPE VARCHAR2(8) NOT NULL, + START_TIME NUMBER(13) NOT NULL, + END_TIME NUMBER(13) NULL, + CALENDAR_NAME VARCHAR2(80) NULL, + MISFIRE_INSTR NUMBER(2) NULL, + JOB_DATA BLOB(4K) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) +); + +CREATE TABLE qrtz_simple_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR2(80) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + REPEAT_COUNT NUMBER(7) NOT NULL, + REPEAT_INTERVAL NUMBER(12) NOT NULL, + TIMES_TRIGGERED NUMBER(10) NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + + +CREATE TABLE qrtz_simprop_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 NUMBER(10) NULL, + INT_PROP_2 NUMBER(10) NULL, + LONG_PROP_1 NUMBER(13) NULL, + LONG_PROP_2 NUMBER(13) NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_cron_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR2(80) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + CRON_EXPRESSION VARCHAR2(120) NOT NULL, + TIME_ZONE_ID VARCHAR2(80), + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_blob_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR2(80) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + BLOB_DATA BLOB(4K) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_calendars + ( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR2(80) NOT NULL, + CALENDAR BLOB(4K) NOT NULL, + PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) +); + +CREATE TABLE qrtz_paused_trigger_grps + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_fired_triggers + ( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR2(95) NOT NULL, + TRIGGER_NAME VARCHAR2(80) NOT NULL, + TRIGGER_GROUP VARCHAR2(80) NOT NULL, + INSTANCE_NAME VARCHAR2(80) NOT NULL, + FIRED_TIME NUMBER(13) NOT NULL, + SCHED_TIME NUMBER(13) NOT NULL, + PRIORITY NUMBER(13) NOT NULL, + STATE VARCHAR2(16) NOT NULL, + JOB_NAME VARCHAR2(80) NULL, + JOB_GROUP VARCHAR2(80) NULL, + IS_NONCONCURRENT BOOLEAN NULL, + REQUESTS_RECOVERY BOOLEAN NULL, + PRIMARY KEY (SCHED_NAME,ENTRY_ID) +); + +CREATE TABLE qrtz_scheduler_state + ( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR2(80) NOT NULL, + LAST_CHECKIN_TIME NUMBER(13) NOT NULL, + CHECKIN_INTERVAL NUMBER(13) NOT NULL, + PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) +); + +CREATE TABLE qrtz_locks + ( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR2(40) NOT NULL, + PRIMARY KEY (SCHED_NAME,LOCK_NAME) +); + +commit; diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_postgres.sql b/demo-task-quartz/init/dbTables/tables_postgres.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_postgres.sql rename to demo-task-quartz/init/dbTables/tables_postgres.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_sapdb.sql b/demo-task-quartz/init/dbTables/tables_sapdb.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_sapdb.sql rename to demo-task-quartz/init/dbTables/tables_sapdb.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_solid.sql b/demo-task-quartz/init/dbTables/tables_solid.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_solid.sql rename to demo-task-quartz/init/dbTables/tables_solid.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_sqlServer.sql b/demo-task-quartz/init/dbTables/tables_sqlServer.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_sqlServer.sql rename to demo-task-quartz/init/dbTables/tables_sqlServer.sql diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_sybase.sql b/demo-task-quartz/init/dbTables/tables_sybase.sql similarity index 100% rename from spring-boot-demo-task-quartz/init/dbTables/tables_sybase.sql rename to demo-task-quartz/init/dbTables/tables_sybase.sql diff --git a/demo-task-quartz/pom.xml b/demo-task-quartz/pom.xml new file mode 100644 index 0000000..51a66c9 --- /dev/null +++ b/demo-task-quartz/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + demo-task-quartz + 1.0.0-SNAPSHOT + jar + + demo-task-quartz + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.0 + 1.2.10 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-quartz + + + + tk.mybatis + mapper-spring-boot-starter + ${mybatis.mapper.version} + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${mybatis.pagehelper.version} + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-task-quartz + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java new file mode 100644 index 0000000..8861ef3 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.task.quartz; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import tk.mybatis.spring.annotation.MapperScan; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-23 20:33 + */ +@MapperScan(basePackages = {"com.xkcoding.task.quartz.mapper"}) +@SpringBootApplication +public class SpringBootDemoTaskQuartzApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTaskQuartzApplication.class, args); + } +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java new file mode 100644 index 0000000..7d27988 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java @@ -0,0 +1,67 @@ +package com.xkcoding.task.quartz.common; + +import lombok.Data; +import org.springframework.http.HttpStatus; + +import java.io.Serializable; + +/** + *

    + * 通用Api封装 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:59 + */ +@Data +public class ApiResponse implements Serializable { + /** + * 返回信息 + */ + private String message; + + /** + * 返回数据 + */ + private Object data; + + public ApiResponse() { + } + + private ApiResponse(String message, Object data) { + this.message = message; + this.data = data; + } + + /** + * 通用封装获取ApiResponse对象 + * + * @param message 返回信息 + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse of(String message, Object data) { + return new ApiResponse(message, data); + } + + /** + * 通用成功封装获取ApiResponse对象 + * + * @param data 返回数据 + * @return ApiResponse + */ + public static ApiResponse ok(Object data) { + return new ApiResponse(HttpStatus.OK.getReasonPhrase(), data); + } + + /** + * 通用封装获取ApiResponse对象 + * + * @param message 返回信息 + * @return ApiResponse + */ + public static ApiResponse msg(String message) { + return of(message, null); + } + +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java new file mode 100644 index 0000000..c1d9ebf --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java @@ -0,0 +1,118 @@ +package com.xkcoding.task.quartz.controller; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.github.pagehelper.PageInfo; +import com.xkcoding.task.quartz.common.ApiResponse; +import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; +import com.xkcoding.task.quartz.entity.form.JobForm; +import com.xkcoding.task.quartz.service.JobService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + *

    + * Job Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:23 + */ +@RestController +@RequestMapping("/job") +@Slf4j +public class JobController { + private final JobService jobService; + + @Autowired + public JobController(JobService jobService) { + this.jobService = jobService; + } + + /** + * 保存定时任务 + */ + @PostMapping + public ResponseEntity addJob(@Valid JobForm form) { + try { + jobService.addJob(form); + } catch (Exception e) { + return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } + + return new ResponseEntity<>(ApiResponse.msg("操作成功"), HttpStatus.CREATED); + } + + /** + * 删除定时任务 + */ + @DeleteMapping + public ResponseEntity deleteJob(JobForm form) throws SchedulerException { + if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { + return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); + } + + jobService.deleteJob(form); + return new ResponseEntity<>(ApiResponse.msg("删除成功"), HttpStatus.OK); + } + + /** + * 暂停定时任务 + */ + @PutMapping(params = "pause") + public ResponseEntity pauseJob(JobForm form) throws SchedulerException { + if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { + return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); + } + + jobService.pauseJob(form); + return new ResponseEntity<>(ApiResponse.msg("暂停成功"), HttpStatus.OK); + } + + /** + * 恢复定时任务 + */ + @PutMapping(params = "resume") + public ResponseEntity resumeJob(JobForm form) throws SchedulerException { + if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { + return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); + } + + jobService.resumeJob(form); + return new ResponseEntity<>(ApiResponse.msg("恢复成功"), HttpStatus.OK); + } + + /** + * 修改定时任务,定时时间 + */ + @PutMapping(params = "cron") + public ResponseEntity cronJob(@Valid JobForm form) { + try { + jobService.cronJob(form); + } catch (Exception e) { + return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } + + return new ResponseEntity<>(ApiResponse.msg("修改成功"), HttpStatus.OK); + } + + @GetMapping + public ResponseEntity jobList(Integer currentPage, Integer pageSize) { + if (ObjectUtil.isNull(currentPage)) { + currentPage = 1; + } + if (ObjectUtil.isNull(pageSize)) { + pageSize = 10; + } + PageInfo all = jobService.list(currentPage, pageSize); + return ResponseEntity.ok(ApiResponse.ok(Dict.create().set("total", all.getTotal()).set("data", all.getList()))); + } + +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java new file mode 100644 index 0000000..c94bce1 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java @@ -0,0 +1,57 @@ +package com.xkcoding.task.quartz.entity.domain; + +import lombok.Data; + +import java.math.BigInteger; + +/** + *

    + * 实体类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 15:05 + */ +@Data +public class JobAndTrigger { + /** + * 定时任务名称 + */ + private String jobName; + /** + * 定时任务组 + */ + private String jobGroup; + /** + * 定时任务全类名 + */ + private String jobClassName; + /** + * 触发器名称 + */ + private String triggerName; + /** + * 触发器组 + */ + private String triggerGroup; + /** + * 重复间隔 + */ + private BigInteger repeatInterval; + /** + * 触发次数 + */ + private BigInteger timesTriggered; + /** + * cron 表达式 + */ + private String cronExpression; + /** + * 时区 + */ + private String timeZoneId; + /** + * 定时任务状态 + */ + private String triggerState; +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java new file mode 100644 index 0000000..c91781f --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java @@ -0,0 +1,34 @@ +package com.xkcoding.task.quartz.entity.form; + +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; + +/** + *

    + * 定时任务详情 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:42 + */ +@Data +@Accessors(chain = true) +public class JobForm { + /** + * 定时任务全类名 + */ + @NotBlank(message = "类名不能为空") + private String jobClassName; + /** + * 任务组名 + */ + @NotBlank(message = "任务组名不能为空") + private String jobGroupName; + /** + * 定时任务cron表达式 + */ + @NotBlank(message = "cron表达式不能为空") + private String cronExpression; +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java new file mode 100644 index 0000000..b1579ff --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java @@ -0,0 +1,23 @@ +package com.xkcoding.task.quartz.job; + +import cn.hutool.core.date.DateUtil; +import com.xkcoding.task.quartz.job.base.BaseJob; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobExecutionContext; + +/** + *

    + * Hello Job + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:22 + */ +@Slf4j +public class HelloJob implements BaseJob { + + @Override + public void execute(JobExecutionContext context) { + log.error("Hello Job 执行时间: {}", DateUtil.now()); + } +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java new file mode 100644 index 0000000..ec41d1e --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java @@ -0,0 +1,23 @@ +package com.xkcoding.task.quartz.job; + +import cn.hutool.core.date.DateUtil; +import com.xkcoding.task.quartz.job.base.BaseJob; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobExecutionContext; + +/** + *

    + * Test Job + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:22 + */ +@Slf4j +public class TestJob implements BaseJob { + + @Override + public void execute(JobExecutionContext context) { + log.error("Test Job 执行时间: {}", DateUtil.now()); + } +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java new file mode 100644 index 0000000..d0343f7 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java @@ -0,0 +1,35 @@ +package com.xkcoding.task.quartz.job.base; + +import org.quartz.*; + +/** + *

    + * Job 基类,主要是在 {@link org.quartz.Job} 上再封装一层,只让我们自己项目里的Job去实现 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:27 + */ +public interface BaseJob extends Job { + /** + *

    + * Called by the {@link Scheduler} when a {@link Trigger} + * fires that is associated with the Job. + *

    + * + *

    + * The implementation may wish to set a + * {@link JobExecutionContext#setResult(Object) result} object on the + * {@link JobExecutionContext} before this method exits. The result itself + * is meaningless to Quartz, but may be informative to + * {@link JobListener}s or + * {@link TriggerListener}s that are watching the job's + * execution. + *

    + * + * @param context 上下文 + * @throws JobExecutionException if there is an exception while executing the job. + */ + @Override + void execute(JobExecutionContext context) throws JobExecutionException; +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java new file mode 100644 index 0000000..cd4d28b --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java @@ -0,0 +1,24 @@ +package com.xkcoding.task.quartz.mapper; + +import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + *

    + * Job Mapper + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 15:12 + */ +@Component +public interface JobMapper { + /** + * 查询定时作业和触发器列表 + * + * @return 定时作业和触发器列表 + */ + List list(); +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java new file mode 100644 index 0000000..33f5184 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java @@ -0,0 +1,65 @@ +package com.xkcoding.task.quartz.service; + +import com.github.pagehelper.PageInfo; +import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; +import com.xkcoding.task.quartz.entity.form.JobForm; +import org.quartz.SchedulerException; + +/** + *

    + * Job Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:24 + */ +public interface JobService { + /** + * 添加并启动定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws Exception 异常 + */ + void addJob(JobForm form) throws Exception; + + /** + * 删除定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + void deleteJob(JobForm form) throws SchedulerException; + + /** + * 暂停定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + void pauseJob(JobForm form) throws SchedulerException; + + /** + * 恢复定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + void resumeJob(JobForm form) throws SchedulerException; + + /** + * 重新配置定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws Exception 异常 + */ + void cronJob(JobForm form) throws Exception; + + /** + * 查询定时任务列表 + * + * @param currentPage 当前页 + * @param pageSize 每页条数 + * @return 定时任务列表 + */ + PageInfo list(Integer currentPage, Integer pageSize); +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java new file mode 100644 index 0000000..9cb88d6 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java @@ -0,0 +1,141 @@ +package com.xkcoding.task.quartz.service.impl; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; +import com.xkcoding.task.quartz.entity.form.JobForm; +import com.xkcoding.task.quartz.mapper.JobMapper; +import com.xkcoding.task.quartz.service.JobService; +import com.xkcoding.task.quartz.util.JobUtil; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

    + * Job Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:25 + */ +@Service +@Slf4j +public class JobServiceImpl implements JobService { + private final Scheduler scheduler; + private final JobMapper jobMapper; + + @Autowired + public JobServiceImpl(Scheduler scheduler, JobMapper jobMapper) { + this.scheduler = scheduler; + this.jobMapper = jobMapper; + } + + /** + * 添加并启动定时任务 + * + * @param form 表单参数 {@link JobForm} + * @return {@link JobDetail} + * @throws Exception 异常 + */ + @Override + public void addJob(JobForm form) throws Exception { + // 启动调度器 + scheduler.start(); + + // 构建Job信息 + JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobClassName(), form.getJobGroupName()).build(); + + // Cron表达式调度构建器(即任务执行的时间) + CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getCronExpression()); + + //根据Cron表达式构建一个Trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobClassName(), form.getJobGroupName()).withSchedule(cron).build(); + + try { + scheduler.scheduleJob(jobDetail, trigger); + } catch (SchedulerException e) { + log.error("【定时任务】创建失败!", e); + throw new Exception("【定时任务】创建失败!"); + } + + } + + /** + * 删除定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + @Override + public void deleteJob(JobForm form) throws SchedulerException { + scheduler.pauseTrigger(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName())); + scheduler.unscheduleJob(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName())); + scheduler.deleteJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); + } + + /** + * 暂停定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + @Override + public void pauseJob(JobForm form) throws SchedulerException { + scheduler.pauseJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); + } + + /** + * 恢复定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws SchedulerException 异常 + */ + @Override + public void resumeJob(JobForm form) throws SchedulerException { + scheduler.resumeJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); + } + + /** + * 重新配置定时任务 + * + * @param form 表单参数 {@link JobForm} + * @throws Exception 异常 + */ + @Override + public void cronJob(JobForm form) throws Exception { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()); + // 表达式调度构建器 + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(form.getCronExpression()); + + CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + + // 根据Cron表达式构建一个Trigger + trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); + + // 按新的trigger重新设置job执行 + scheduler.rescheduleJob(triggerKey, trigger); + } catch (SchedulerException e) { + log.error("【定时任务】更新失败!", e); + throw new Exception("【定时任务】创建失败!"); + } + } + + /** + * 查询定时任务列表 + * + * @param currentPage 当前页 + * @param pageSize 每页条数 + * @return 定时任务列表 + */ + @Override + public PageInfo list(Integer currentPage, Integer pageSize) { + PageHelper.startPage(currentPage, pageSize); + List list = jobMapper.list(); + return new PageInfo<>(list); + } +} diff --git a/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java new file mode 100644 index 0000000..ab98b10 --- /dev/null +++ b/demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java @@ -0,0 +1,25 @@ +package com.xkcoding.task.quartz.util; + +import com.xkcoding.task.quartz.job.base.BaseJob; + +/** + *

    + * 定时任务反射工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-26 13:33 + */ +public class JobUtil { + /** + * 根据全类名获取Job实例 + * + * @param classname Job全类名 + * @return {@link BaseJob} 实例 + * @throws Exception 泛型获取异常 + */ + public static BaseJob getClass(String classname) throws Exception { + Class clazz = Class.forName(classname); + return (BaseJob) clazz.newInstance(); + } +} diff --git a/spring-boot-demo-task-quartz/src/main/resources/application.yml b/demo-task-quartz/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-task-quartz/src/main/resources/application.yml rename to demo-task-quartz/src/main/resources/application.yml diff --git a/spring-boot-demo-task-quartz/src/main/resources/mappers/JobMapper.xml b/demo-task-quartz/src/main/resources/mappers/JobMapper.xml similarity index 100% rename from spring-boot-demo-task-quartz/src/main/resources/mappers/JobMapper.xml rename to demo-task-quartz/src/main/resources/mappers/JobMapper.xml diff --git a/spring-boot-demo-task-quartz/src/main/resources/static/job.html b/demo-task-quartz/src/main/resources/static/job.html similarity index 100% rename from spring-boot-demo-task-quartz/src/main/resources/static/job.html rename to demo-task-quartz/src/main/resources/static/job.html diff --git a/spring-boot-demo-task-quartz/src/test/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplicationTests.java b/demo-task-quartz/src/test/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplicationTests.java similarity index 100% rename from spring-boot-demo-task-quartz/src/test/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplicationTests.java rename to demo-task-quartz/src/test/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplicationTests.java diff --git a/spring-boot-demo-task/.gitignore b/demo-task-xxl-job/.gitignore similarity index 100% rename from spring-boot-demo-task/.gitignore rename to demo-task-xxl-job/.gitignore diff --git a/spring-boot-demo-task-xxl-job/README.md b/demo-task-xxl-job/README.md similarity index 100% rename from spring-boot-demo-task-xxl-job/README.md rename to demo-task-xxl-job/README.md diff --git a/demo-task-xxl-job/pom.xml b/demo-task-xxl-job/pom.xml new file mode 100644 index 0000000..23dae70 --- /dev/null +++ b/demo-task-xxl-job/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + demo-task-xxl-job + 1.0.0-SNAPSHOT + jar + + demo-task-xxl-job + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 2.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + com.xuxueli + xxl-job-core + ${xxl-job.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-task-xxl-job + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/SpringBootDemoTaskXxlJobApplication.java b/demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/SpringBootDemoTaskXxlJobApplication.java similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/SpringBootDemoTaskXxlJobApplication.java rename to demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/SpringBootDemoTaskXxlJobApplication.java diff --git a/spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/XxlJobConfig.java b/demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/XxlJobConfig.java similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/XxlJobConfig.java rename to demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/XxlJobConfig.java diff --git a/spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/props/XxlJobProps.java b/demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/props/XxlJobProps.java similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/props/XxlJobProps.java rename to demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/config/props/XxlJobProps.java diff --git a/spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/controller/ManualOperateController.java b/demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/controller/ManualOperateController.java similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/controller/ManualOperateController.java rename to demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/controller/ManualOperateController.java diff --git a/spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/task/DemoTask.java b/demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/task/DemoTask.java similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/task/DemoTask.java rename to demo-task-xxl-job/src/main/java/com/xkcoding/task/xxl/job/task/DemoTask.java diff --git a/spring-boot-demo-task-xxl-job/src/main/resources/application.yml b/demo-task-xxl-job/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-task-xxl-job/src/main/resources/application.yml rename to demo-task-xxl-job/src/main/resources/application.yml diff --git a/spring-boot-demo-template-beetl/.gitignore b/demo-task/.gitignore similarity index 100% rename from spring-boot-demo-template-beetl/.gitignore rename to demo-task/.gitignore diff --git a/demo-task/README.md b/demo-task/README.md new file mode 100644 index 0000000..c56646e --- /dev/null +++ b/demo-task/README.md @@ -0,0 +1,175 @@ +# spring-boot-demo-task + +> 此 demo 主要演示了 Spring Boot 如何快速实现定时任务。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-task + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-task + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.commons + commons-lang3 + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-task + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## TaskConfig.java + +> 此处等同于在配置文件配置 +> +> ```properties +> spring.task.scheduling.pool.size=20 +> spring.task.scheduling.thread-name-prefix=Job-Thread- +> ``` + +```java +/** + *

    + * 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-22 19:02 + */ +@Configuration +@EnableScheduling +@ComponentScan(basePackages = {"com.xkcoding.task.job"}) +public class TaskConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + } + + /** + * 这里等同于配置文件配置 + * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. + * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. + * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} + */ + @Bean + public Executor taskExecutor() { + return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); + } +} +``` + +## TaskJob.java + +```java +/** + *

    + * 定时任务 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-22 19:09 + */ +@Component +@Slf4j +public class TaskJob { + + /** + * 按照标准时间来算,每隔 10s 执行一次 + */ + @Scheduled(cron = "0/10 * * * * ?") + public void job1() { + log.info("【job1】开始执行:{}", DateUtil.formatDateTime(new Date())); + } + + /** + * 从启动时间开始,间隔 2s 执行 + * 固定间隔时间 + */ + @Scheduled(fixedRate = 2000) + public void job2() { + log.info("【job2】开始执行:{}", DateUtil.formatDateTime(new Date())); + } + + /** + * 从启动时间开始,延迟 5s 后间隔 4s 执行 + * 固定等待时间 + */ + @Scheduled(fixedDelay = 4000, initialDelay = 5000) + public void job3() { + log.info("【job3】开始执行:{}", DateUtil.formatDateTime(new Date())); + } +} +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +# 下面的配置等同于 TaskConfig +#spring: +# task: +# scheduling: +# pool: +# size: 20 +# thread-name-prefix: Job-Thread- +``` + +## 参考 + +- Spring Boot官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-task-execution-scheduling diff --git a/demo-task/pom.xml b/demo-task/pom.xml new file mode 100644 index 0000000..afae3c7 --- /dev/null +++ b/demo-task/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + demo-task + 1.0.0-SNAPSHOT + jar + + demo-task + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.commons + commons-lang3 + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-task + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java b/demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java new file mode 100644 index 0000000..8f75a00 --- /dev/null +++ b/demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.task; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-22 19:00 + */ +@SpringBootApplication +public class SpringBootDemoTaskApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTaskApplication.class, args); + } +} diff --git a/demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java b/demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java new file mode 100644 index 0000000..9a00e9e --- /dev/null +++ b/demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java @@ -0,0 +1,41 @@ +package com.xkcoding.task.config; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + *

    + * 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-22 19:02 + */ +@Configuration +@EnableScheduling +@ComponentScan(basePackages = {"com.xkcoding.task.job"}) +public class TaskConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + } + + /** + * 这里等同于配置文件配置 + * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. + * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. + * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} + */ + @Bean + public Executor taskExecutor() { + return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); + } +} diff --git a/demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java b/demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java new file mode 100644 index 0000000..94965e1 --- /dev/null +++ b/demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java @@ -0,0 +1,47 @@ +package com.xkcoding.task.job; + +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + *

    + * 定时任务 + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-22 19:09 + */ +@Component +@Slf4j +public class TaskJob { + + /** + * 按照标准时间来算,每隔 10s 执行一次 + */ + @Scheduled(cron = "0/10 * * * * ?") + public void job1() { + log.info("【job1】开始执行:{}", DateUtil.formatDateTime(new Date())); + } + + /** + * 从启动时间开始,间隔 2s 执行 + * 固定间隔时间 + */ + @Scheduled(fixedRate = 2000) + public void job2() { + log.info("【job2】开始执行:{}", DateUtil.formatDateTime(new Date())); + } + + /** + * 从启动时间开始,延迟 5s 后间隔 4s 执行 + * 固定等待时间 + */ + @Scheduled(fixedDelay = 4000, initialDelay = 5000) + public void job3() { + log.info("【job3】开始执行:{}", DateUtil.formatDateTime(new Date())); + } +} diff --git a/spring-boot-demo-task/src/main/resources/application.yml b/demo-task/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-task/src/main/resources/application.yml rename to demo-task/src/main/resources/application.yml diff --git a/spring-boot-demo-task/src/test/java/com/xkcoding/task/SpringBootDemoTaskApplicationTests.java b/demo-task/src/test/java/com/xkcoding/task/SpringBootDemoTaskApplicationTests.java similarity index 100% rename from spring-boot-demo-task/src/test/java/com/xkcoding/task/SpringBootDemoTaskApplicationTests.java rename to demo-task/src/test/java/com/xkcoding/task/SpringBootDemoTaskApplicationTests.java diff --git a/spring-boot-demo-template-enjoy/.gitignore b/demo-template-beetl/.gitignore similarity index 100% rename from spring-boot-demo-template-enjoy/.gitignore rename to demo-template-beetl/.gitignore diff --git a/demo-template-beetl/README.md b/demo-template-beetl/README.md new file mode 100644 index 0000000..fffc08e --- /dev/null +++ b/demo-template-beetl/README.md @@ -0,0 +1,185 @@ +# spring-boot-demo-template-beetl + +> 本 demo 主要演示了 Spring Boot 项目如何集成 beetl 模板引擎 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-template-beetl + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-template-beetl + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.63.RELEASE + + + + + com.ibeetl + beetl-framework-starter + ${ibeetl.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-template-beetl + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## IndexController.java + +```java +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:17 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index.btl"); + mv.addObject(user); + } + + return mv; + } +} +``` + +## UserController.java + +```java +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:17 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login.btl"); + } +} +``` + +## index.html + +```jsp + + +<% include("/common/head.html"){} %> + +
    + 欢迎登录,${user.name}! +
    + + +``` + +## login.html + +```jsp + + +<% include("/common/head.html"){} %> + +
    +
    + 用户名 + 密码 + +
    +
    + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +``` + +## Beetl 语法糖学习文档 + +http://ibeetl.com/guide/#beetl + diff --git a/demo-template-beetl/pom.xml b/demo-template-beetl/pom.xml new file mode 100644 index 0000000..56c043f --- /dev/null +++ b/demo-template-beetl/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + demo-template-beetl + 1.0.0-SNAPSHOT + jar + + demo-template-beetl + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.1.63.RELEASE + + + + + com.ibeetl + beetl-framework-starter + ${ibeetl.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + demo-template-beetl + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java new file mode 100644 index 0000000..9127777 --- /dev/null +++ b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.template.beetl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:17 + */ +@SpringBootApplication +public class SpringBootDemoTemplateBeetlApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTemplateBeetlApplication.class, args); + } +} diff --git a/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java new file mode 100644 index 0000000..c710f7a --- /dev/null +++ b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java @@ -0,0 +1,38 @@ +package com.xkcoding.template.beetl.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.template.beetl.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:17 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index.btl"); + mv.addObject(user); + } + + return mv; + } +} diff --git a/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java new file mode 100644 index 0000000..385a5f9 --- /dev/null +++ b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java @@ -0,0 +1,40 @@ +package com.xkcoding.template.beetl.controller; + +import com.xkcoding.template.beetl.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:17 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login.btl"); + } +} diff --git a/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java new file mode 100644 index 0000000..d19b361 --- /dev/null +++ b/demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java @@ -0,0 +1,17 @@ +package com.xkcoding.template.beetl.model; + +import lombok.Data; + +/** + *

    + * 用户 model + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 11:18 + */ +@Data +public class User { + private String name; + private String password; +} diff --git a/spring-boot-demo-template-enjoy/src/main/resources/application.yml b/demo-template-beetl/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-template-enjoy/src/main/resources/application.yml rename to demo-template-beetl/src/main/resources/application.yml diff --git a/spring-boot-demo-template-beetl/src/main/resources/templates/common/head.html b/demo-template-beetl/src/main/resources/templates/common/head.html similarity index 100% rename from spring-boot-demo-template-beetl/src/main/resources/templates/common/head.html rename to demo-template-beetl/src/main/resources/templates/common/head.html diff --git a/spring-boot-demo-template-beetl/src/main/resources/templates/page/index.btl b/demo-template-beetl/src/main/resources/templates/page/index.btl similarity index 100% rename from spring-boot-demo-template-beetl/src/main/resources/templates/page/index.btl rename to demo-template-beetl/src/main/resources/templates/page/index.btl diff --git a/spring-boot-demo-template-beetl/src/main/resources/templates/page/login.btl b/demo-template-beetl/src/main/resources/templates/page/login.btl similarity index 100% rename from spring-boot-demo-template-beetl/src/main/resources/templates/page/login.btl rename to demo-template-beetl/src/main/resources/templates/page/login.btl diff --git a/demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java b/demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java new file mode 100644 index 0000000..4952bfd --- /dev/null +++ b/demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.template.beetl; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoTemplateBeetlApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-template-freemarker/.gitignore b/demo-template-enjoy/.gitignore similarity index 100% rename from spring-boot-demo-template-freemarker/.gitignore rename to demo-template-enjoy/.gitignore diff --git a/demo-template-enjoy/README.md b/demo-template-enjoy/README.md new file mode 100644 index 0000000..2d16929 --- /dev/null +++ b/demo-template-enjoy/README.md @@ -0,0 +1,220 @@ +# spring-boot-demo-template-enjoy + +> 本 demo 主要演示了 Spring Boot 项目如何集成 enjoy 模板引擎。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-template-enjoy + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-template-enjoy + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.5 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.jfinal + enjoy + ${enjoy.version} + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-template-enjoy + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## EnjoyConfig.java + +```java +/** + *

    + * Enjoy 模板配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:06 + */ +@Configuration +public class EnjoyConfig { + @Bean(name = "jfinalViewResolver") + public JFinalViewResolver getJFinalViewResolver() { + JFinalViewResolver jfr = new JFinalViewResolver(); + // setDevMode 配置放在最前面 + jfr.setDevMode(true); + // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件 + jfr.setSourceFactory(new ClassPathSourceFactory()); + // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath + // 代替 jfr.setPrefix("/view/") + JFinalViewResolver.engine.setBaseTemplatePath("/templates/"); + + jfr.setSessionInView(true); + jfr.setSuffix(".html"); + jfr.setContentType("text/html;charset=UTF-8"); + jfr.setOrder(0); + return jfr; + } +} +``` + +## IndexController.java + +```java +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:22 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index"); + mv.addObject(user); + } + + return mv; + } +} +``` + +## UserController.java + +```java +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:24 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login"); + } +} +``` + +## index.html + +```jsp + + +#include("/common/head.html") + +
    + 欢迎登录,#(user.name)! +
    + + +``` + +## login.html + +```jsp + + +#include("/common/head.html") + +
    +
    + 用户名 + 密码 + +
    +
    + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +``` + +## Enjoy 语法糖学习文档 + +http://www.jfinal.com/doc/6-1 + + + diff --git a/demo-template-enjoy/pom.xml b/demo-template-enjoy/pom.xml new file mode 100644 index 0000000..c21fe3f --- /dev/null +++ b/demo-template-enjoy/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + demo-template-enjoy + 1.0.0-SNAPSHOT + jar + + demo-template-enjoy + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.5 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.jfinal + enjoy + ${enjoy.version} + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + demo-template-enjoy + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java new file mode 100644 index 0000000..79dc600 --- /dev/null +++ b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.template.enjoy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:06 + */ +@SpringBootApplication +public class SpringBootDemoTemplateEnjoyApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTemplateEnjoyApplication.class, args); + } +} diff --git a/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java new file mode 100644 index 0000000..d7d863b --- /dev/null +++ b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java @@ -0,0 +1,35 @@ +package com.xkcoding.template.enjoy.config; + +import com.jfinal.template.ext.spring.JFinalViewResolver; +import com.jfinal.template.source.ClassPathSourceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

    + * Enjoy 模板配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:06 + */ +@Configuration +public class EnjoyConfig { + @Bean(name = "jfinalViewResolver") + public JFinalViewResolver getJFinalViewResolver() { + JFinalViewResolver jfr = new JFinalViewResolver(); + // setDevMode 配置放在最前面 + jfr.setDevMode(true); + // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件 + jfr.setSourceFactory(new ClassPathSourceFactory()); + // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath + // 代替 jfr.setPrefix("/view/") + JFinalViewResolver.engine.setBaseTemplatePath("/templates/"); + + jfr.setSessionInView(true); + jfr.setSuffix(".html"); + jfr.setContentType("text/html;charset=UTF-8"); + jfr.setOrder(0); + return jfr; + } +} diff --git a/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java new file mode 100644 index 0000000..35f9df2 --- /dev/null +++ b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java @@ -0,0 +1,38 @@ +package com.xkcoding.template.enjoy.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.template.enjoy.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:22 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index"); + mv.addObject(user); + } + + return mv; + } +} diff --git a/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java new file mode 100644 index 0000000..76d7630 --- /dev/null +++ b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java @@ -0,0 +1,40 @@ +package com.xkcoding.template.enjoy.controller; + +import com.xkcoding.template.enjoy.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:24 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login"); + } +} diff --git a/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java new file mode 100644 index 0000000..bdd9896 --- /dev/null +++ b/demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java @@ -0,0 +1,17 @@ +package com.xkcoding.template.enjoy.model; + +import lombok.Data; + +/** + *

    + * 用户 model + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-11 14:21 + */ +@Data +public class User { + private String name; + private String password; +} diff --git a/spring-boot-demo-war/src/main/resources/application.yml b/demo-template-enjoy/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-war/src/main/resources/application.yml rename to demo-template-enjoy/src/main/resources/application.yml diff --git a/spring-boot-demo-template-enjoy/src/main/resources/templates/common/head.html b/demo-template-enjoy/src/main/resources/templates/common/head.html similarity index 100% rename from spring-boot-demo-template-enjoy/src/main/resources/templates/common/head.html rename to demo-template-enjoy/src/main/resources/templates/common/head.html diff --git a/spring-boot-demo-template-enjoy/src/main/resources/templates/page/index.html b/demo-template-enjoy/src/main/resources/templates/page/index.html similarity index 100% rename from spring-boot-demo-template-enjoy/src/main/resources/templates/page/index.html rename to demo-template-enjoy/src/main/resources/templates/page/index.html diff --git a/spring-boot-demo-template-enjoy/src/main/resources/templates/page/login.html b/demo-template-enjoy/src/main/resources/templates/page/login.html similarity index 100% rename from spring-boot-demo-template-enjoy/src/main/resources/templates/page/login.html rename to demo-template-enjoy/src/main/resources/templates/page/login.html diff --git a/demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java b/demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java new file mode 100644 index 0000000..69f9ea4 --- /dev/null +++ b/demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.template.enjoy; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoTemplateEnjoyApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-template-thymeleaf/.gitignore b/demo-template-freemarker/.gitignore similarity index 100% rename from spring-boot-demo-template-thymeleaf/.gitignore rename to demo-template-freemarker/.gitignore diff --git a/demo-template-freemarker/README.md b/demo-template-freemarker/README.md new file mode 100644 index 0000000..bd22e2b --- /dev/null +++ b/demo-template-freemarker/README.md @@ -0,0 +1,188 @@ +# spring-boot-demo-template-freemarker + +> 本 demo 主要演示了 Spring Boot 项目如何集成 freemarker 模板引擎 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-template-freemarker + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-template-freemarker + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-freemarker + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-template-freemarker + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## IndexController.java + +```java +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-019 15:07 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("index"); + mv.addObject(user); + } + + return mv; + } +} +``` + +## UserController.java + +```java +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-019 15:11 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("login"); + } +} +``` + +## index.ftl + +```jsp + + +<#include "./common/head.ftl"> + +
    + 欢迎登录,${user.name}! +
    + + +``` + +## login.ftl + +```jsp + + +<#include "./common/head.ftl"> + +
    +
    + 用户名 + 密码 + +
    +
    + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + freemarker: + suffix: .ftl + cache: false + charset: UTF-8 +``` + +## Freemarker 语法糖学习文档 + +https://freemarker.apache.org/docs/dgui.html + diff --git a/demo-template-freemarker/pom.xml b/demo-template-freemarker/pom.xml new file mode 100644 index 0000000..0266366 --- /dev/null +++ b/demo-template-freemarker/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + demo-template-freemarker + 1.0.0-SNAPSHOT + jar + + demo-template-freemarker + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-freemarker + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + demo-template-freemarker + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java new file mode 100644 index 0000000..f2dd0fd --- /dev/null +++ b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.template.freemarker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-19 15:17 + */ +@SpringBootApplication +public class SpringBootDemoTemplateFreemarkerApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTemplateFreemarkerApplication.class, args); + } +} diff --git a/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java new file mode 100644 index 0000000..c315e06 --- /dev/null +++ b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java @@ -0,0 +1,38 @@ +package com.xkcoding.template.freemarker.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.template.freemarker.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-19 15:07 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index"); + mv.addObject(user); + } + + return mv; + } +} diff --git a/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java new file mode 100644 index 0000000..b9e6f5d --- /dev/null +++ b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java @@ -0,0 +1,40 @@ +package com.xkcoding.template.freemarker.controller; + +import com.xkcoding.template.freemarker.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-19 15:11 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login"); + } +} diff --git a/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java new file mode 100644 index 0000000..457e658 --- /dev/null +++ b/demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java @@ -0,0 +1,17 @@ +package com.xkcoding.template.freemarker.model; + +import lombok.Data; + +/** + *

    + * 用户 model + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-19 15:06 + */ +@Data +public class User { + private String name; + private String password; +} diff --git a/spring-boot-demo-template-freemarker/src/main/resources/application.yml b/demo-template-freemarker/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-template-freemarker/src/main/resources/application.yml rename to demo-template-freemarker/src/main/resources/application.yml diff --git a/spring-boot-demo-template-freemarker/src/main/resources/templates/common/head.ftl b/demo-template-freemarker/src/main/resources/templates/common/head.ftl similarity index 100% rename from spring-boot-demo-template-freemarker/src/main/resources/templates/common/head.ftl rename to demo-template-freemarker/src/main/resources/templates/common/head.ftl diff --git a/spring-boot-demo-template-freemarker/src/main/resources/templates/page/index.ftl b/demo-template-freemarker/src/main/resources/templates/page/index.ftl similarity index 100% rename from spring-boot-demo-template-freemarker/src/main/resources/templates/page/index.ftl rename to demo-template-freemarker/src/main/resources/templates/page/index.ftl diff --git a/spring-boot-demo-template-freemarker/src/main/resources/templates/page/login.ftl b/demo-template-freemarker/src/main/resources/templates/page/login.ftl similarity index 100% rename from spring-boot-demo-template-freemarker/src/main/resources/templates/page/login.ftl rename to demo-template-freemarker/src/main/resources/templates/page/login.ftl diff --git a/demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java b/demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java new file mode 100644 index 0000000..d5e691d --- /dev/null +++ b/demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.template.freemarker; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoTemplateFreemarkerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-uflo/.gitignore b/demo-template-thymeleaf/.gitignore similarity index 100% rename from spring-boot-demo-uflo/.gitignore rename to demo-template-thymeleaf/.gitignore diff --git a/demo-template-thymeleaf/README.md b/demo-template-thymeleaf/README.md new file mode 100644 index 0000000..e588d1e --- /dev/null +++ b/demo-template-thymeleaf/README.md @@ -0,0 +1,190 @@ +# spring-boot-demo-template-thymeleaf + +> 本 demo 主要演示了 Spring Boot 项目如何集成 thymeleaf 模板引擎 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-template-thymeleaf + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-template-thymeleaf + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + spring-boot-demo-template-thymeleaf + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## IndexController.java + +```java +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:12 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index"); + mv.addObject(user); + } + + return mv; + } +} +``` + +## UserController.java + +```java +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:11 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login"); + } +} +``` + +## index.html + +```jsp + + +
    + +
    + 欢迎登录,! +
    + + +``` + +## login.html + +```jsp + + +
    + +
    +
    + 用户名 + 密码 + +
    +
    + + +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + thymeleaf: + mode: HTML + encoding: UTF-8 + servlet: + content-type: text/html + cache: false +``` + +## Thymeleaf语法糖学习文档 + +https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html + diff --git a/demo-template-thymeleaf/pom.xml b/demo-template-thymeleaf/pom.xml new file mode 100644 index 0000000..544065f --- /dev/null +++ b/demo-template-thymeleaf/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + demo-template-thymeleaf + 1.0.0-SNAPSHOT + jar + + demo-template-thymeleaf + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + demo-template-thymeleaf + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java new file mode 100644 index 0000000..3a850bd --- /dev/null +++ b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.template.thymeleaf; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:10 + */ +@SpringBootApplication +public class SpringBootDemoTemplateThymeleafApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTemplateThymeleafApplication.class, args); + } +} diff --git a/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java new file mode 100644 index 0000000..df6bcab --- /dev/null +++ b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java @@ -0,0 +1,38 @@ +package com.xkcoding.template.thymeleaf.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.xkcoding.template.thymeleaf.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 主页 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:12 + */ +@Controller +@Slf4j +public class IndexController { + + @GetMapping(value = {"", "/"}) + public ModelAndView index(HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + User user = (User) request.getSession().getAttribute("user"); + if (ObjectUtil.isNull(user)) { + mv.setViewName("redirect:/user/login"); + } else { + mv.setViewName("page/index"); + mv.addObject(user); + } + + return mv; + } +} diff --git a/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java new file mode 100644 index 0000000..a395a5a --- /dev/null +++ b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java @@ -0,0 +1,40 @@ +package com.xkcoding.template.thymeleaf.controller; + +import com.xkcoding.template.thymeleaf.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +/** + *

    + * 用户页面 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:11 + */ +@Controller +@RequestMapping("/user") +@Slf4j +public class UserController { + @PostMapping("/login") + public ModelAndView login(User user, HttpServletRequest request) { + ModelAndView mv = new ModelAndView(); + + mv.addObject(user); + mv.setViewName("redirect:/"); + + request.getSession().setAttribute("user", user); + return mv; + } + + @GetMapping("/login") + public ModelAndView login() { + return new ModelAndView("page/login"); + } +} diff --git a/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java new file mode 100644 index 0000000..cf4d5a1 --- /dev/null +++ b/demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java @@ -0,0 +1,17 @@ +package com.xkcoding.template.thymeleaf.model; + +import lombok.Data; + +/** + *

    + * 用户 model + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-10 10:11 + */ +@Data +public class User { + private String name; + private String password; +} diff --git a/spring-boot-demo-template-thymeleaf/src/main/resources/application.yml b/demo-template-thymeleaf/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-template-thymeleaf/src/main/resources/application.yml rename to demo-template-thymeleaf/src/main/resources/application.yml diff --git a/spring-boot-demo-template-thymeleaf/src/main/resources/templates/common/head.html b/demo-template-thymeleaf/src/main/resources/templates/common/head.html similarity index 100% rename from spring-boot-demo-template-thymeleaf/src/main/resources/templates/common/head.html rename to demo-template-thymeleaf/src/main/resources/templates/common/head.html diff --git a/spring-boot-demo-template-thymeleaf/src/main/resources/templates/page/index.html b/demo-template-thymeleaf/src/main/resources/templates/page/index.html similarity index 100% rename from spring-boot-demo-template-thymeleaf/src/main/resources/templates/page/index.html rename to demo-template-thymeleaf/src/main/resources/templates/page/index.html diff --git a/spring-boot-demo-template-thymeleaf/src/main/resources/templates/page/login.html b/demo-template-thymeleaf/src/main/resources/templates/page/login.html similarity index 100% rename from spring-boot-demo-template-thymeleaf/src/main/resources/templates/page/login.html rename to demo-template-thymeleaf/src/main/resources/templates/page/login.html diff --git a/demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java b/demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java new file mode 100644 index 0000000..dd20c0c --- /dev/null +++ b/demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.template.thymeleaf; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoTemplateThymeleafApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-tio/.gitignore b/demo-tio/.gitignore similarity index 100% rename from spring-boot-demo-tio/.gitignore rename to demo-tio/.gitignore diff --git a/spring-boot-demo-tio/README.md b/demo-tio/README.md similarity index 100% rename from spring-boot-demo-tio/README.md rename to demo-tio/README.md diff --git a/demo-tio/pom.xml b/demo-tio/pom.xml new file mode 100644 index 0000000..b3ec5b4 --- /dev/null +++ b/demo-tio/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + demo-tio + 1.0.0-SNAPSHOT + demo-tio + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java b/demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java new file mode 100644 index 0000000..5822865 --- /dev/null +++ b/demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.springbootdemotio; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-02-05 18:58 + */ +@SpringBootApplication +public class SpringBootDemoTioApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoTioApplication.class, args); + } + +} + diff --git a/spring-boot-demo-uflo/src/main/resources/application.properties b/demo-tio/src/main/resources/application.properties similarity index 100% rename from spring-boot-demo-uflo/src/main/resources/application.properties rename to demo-tio/src/main/resources/application.properties diff --git a/spring-boot-demo-tio/src/test/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplicationTests.java b/demo-tio/src/test/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplicationTests.java similarity index 100% rename from spring-boot-demo-tio/src/test/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplicationTests.java rename to demo-tio/src/test/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplicationTests.java diff --git a/spring-boot-demo-upload/.gitignore b/demo-uflo/.gitignore similarity index 100% rename from spring-boot-demo-upload/.gitignore rename to demo-uflo/.gitignore diff --git a/demo-uflo/pom.xml b/demo-uflo/pom.xml new file mode 100644 index 0000000..a87457a --- /dev/null +++ b/demo-uflo/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + demo-uflo + 1.0.0-SNAPSHOT + jar + + demo-uflo + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-uflo + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-demo-uflo/src/main/java/com/xkcoding/uflo/SpringBootDemoUfloApplication.java b/demo-uflo/src/main/java/com/xkcoding/uflo/SpringBootDemoUfloApplication.java similarity index 100% rename from spring-boot-demo-uflo/src/main/java/com/xkcoding/uflo/SpringBootDemoUfloApplication.java rename to demo-uflo/src/main/java/com/xkcoding/uflo/SpringBootDemoUfloApplication.java diff --git a/spring-boot-demo-ureport2/src/main/resources/application.properties b/demo-uflo/src/main/resources/application.properties similarity index 100% rename from spring-boot-demo-ureport2/src/main/resources/application.properties rename to demo-uflo/src/main/resources/application.properties diff --git a/spring-boot-demo-uflo/src/test/java/com/xkcoding/uflo/SpringBootDemoUfloApplicationTests.java b/demo-uflo/src/test/java/com/xkcoding/uflo/SpringBootDemoUfloApplicationTests.java similarity index 100% rename from spring-boot-demo-uflo/src/test/java/com/xkcoding/uflo/SpringBootDemoUfloApplicationTests.java rename to demo-uflo/src/test/java/com/xkcoding/uflo/SpringBootDemoUfloApplicationTests.java diff --git a/spring-boot-demo-ureport2/.gitignore b/demo-upload/.gitignore similarity index 100% rename from spring-boot-demo-ureport2/.gitignore rename to demo-upload/.gitignore diff --git a/demo-upload/README.md b/demo-upload/README.md new file mode 100644 index 0000000..22d01df --- /dev/null +++ b/demo-upload/README.md @@ -0,0 +1,504 @@ +# spring-boot-demo-upload + +> 本 demo 演示了 Spring Boot 如何实现本地文件上传以及如何上传文件至七牛云平台。前端使用 vue 和 iview 实现上传页面。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-upload + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-upload + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.qiniu + qiniu-java-sdk + [7.2.0, 7.2.99] + + + + + spring-boot-demo-upload + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## UploadConfig.java + +```java +/** + *

    + * 上传配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-23 14:09 + */ +@Configuration +@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class}) +@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) +@EnableConfigurationProperties(MultipartProperties.class) +public class UploadConfig { + @Value("${qiniu.accessKey}") + private String accessKey; + + @Value("${qiniu.secretKey}") + private String secretKey; + + private final MultipartProperties multipartProperties; + + @Autowired + public UploadConfig(MultipartProperties multipartProperties) { + this.multipartProperties = multipartProperties; + } + + /** + * 上传配置 + */ + @Bean + @ConditionalOnMissingBean + public MultipartConfigElement multipartConfigElement() { + return this.multipartProperties.createMultipartConfig(); + } + + /** + * 注册解析器 + */ + @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) + @ConditionalOnMissingBean(MultipartResolver.class) + public StandardServletMultipartResolver multipartResolver() { + StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); + multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); + return multipartResolver; + } + + /** + * 华东机房 + */ + @Bean + public com.qiniu.storage.Configuration qiniuConfig() { + return new com.qiniu.storage.Configuration(Zone.zone0()); + } + + /** + * 构建一个七牛上传工具实例 + */ + @Bean + public UploadManager uploadManager() { + return new UploadManager(qiniuConfig()); + } + + /** + * 认证信息实例 + */ + @Bean + public Auth auth() { + return Auth.create(accessKey, secretKey); + } + + /** + * 构建七牛空间管理实例 + */ + @Bean + public BucketManager bucketManager() { + return new BucketManager(auth(), qiniuConfig()); + } +} +``` + +## UploadController.java + +```java +/** + *

    + * 文件上传 Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-06 16:33 + */ +@RestController +@Slf4j +@RequestMapping("/upload") +public class UploadController { + @Value("${spring.servlet.multipart.location}") + private String fileTempPath; + + @Value("${qiniu.prefix}") + private String prefix; + + private final IQiNiuService qiNiuService; + + @Autowired + public UploadController(IQiNiuService qiNiuService) { + this.qiNiuService = qiNiuService; + } + + @PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Dict local(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return Dict.create().set("code", 400).set("message", "文件内容为空"); + } + String fileName = file.getOriginalFilename(); + String rawFileName = StrUtil.subBefore(fileName, ".", true); + String fileType = StrUtil.subAfter(fileName, ".", true); + String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; + try { + file.transferTo(new File(localFilePath)); + } catch (IOException e) { + log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + + log.info("【文件上传至本地】绝对路径:{}", localFilePath); + return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath)); + } + + @PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Dict yun(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return Dict.create().set("code", 400).set("message", "文件内容为空"); + } + String fileName = file.getOriginalFilename(); + String rawFileName = StrUtil.subBefore(fileName, ".", true); + String fileType = StrUtil.subAfter(fileName, ".", true); + String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; + try { + file.transferTo(new File(localFilePath)); + Response response = qiNiuService.uploadFile(new File(localFilePath)); + if (response.isOK()) { + JSONObject jsonObject = JSONUtil.parseObj(response.bodyString()); + + String yunFileName = jsonObject.getStr("key"); + String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName; + + FileUtil.del(new File(localFilePath)); + + log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath); + return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath)); + } else { + log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response)); + FileUtil.del(new File(localFilePath)); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + } catch (IOException e) { + log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + } +} +``` + +## QiNiuServiceImpl.java + +```java +/** + *

    + * 七牛云上传Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-06 17:22 + */ +@Service +@Slf4j +public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { + private final UploadManager uploadManager; + + private final Auth auth; + + @Value("${qiniu.bucket}") + private String bucket; + + private StringMap putPolicy; + + @Autowired + public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) { + this.uploadManager = uploadManager; + this.auth = auth; + } + + /** + * 七牛云上传文件 + * + * @param file 文件 + * @return 七牛上传Response + * @throws QiniuException 七牛异常 + */ + @Override + public Response uploadFile(File file) throws QiniuException { + Response response = this.uploadManager.put(file, file.getName(), getUploadToken()); + int retry = 0; + while (response.needRetry() && retry < 3) { + response = this.uploadManager.put(file, file.getName(), getUploadToken()); + retry++; + } + return response; + } + + @Override + public void afterPropertiesSet() { + this.putPolicy = new StringMap(); + putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}"); + } + + /** + * 获取上传凭证 + * + * @return 上传凭证 + */ + private String getUploadToken() { + return this.auth.uploadToken(bucket, null, 3600, putPolicy); + } +} +``` + +## index.html + +```html + + + + + + + spring-boot-demo-upload + + + + + + + + +
    + + + +

    + + 本地上传 +

    +
    + + 选择文件 + + + {{ local.loadingStatus ? '本地文件上传中' : '本地上传' }} + +
    +
    +
    状态:{{local.log.message}}
    +
    文件名:{{local.log.fileName}}
    +
    文件路径:{{local.log.filePath}}
    +
    +
    +
    + + +

    + + 七牛云上传 +

    +
    + + 选择文件 + + + {{ yun.loadingStatus ? '七牛云文件上传中' : '七牛云上传' }} + +
    +
    +
    状态:{{yun.log.message}}
    +
    文件名:{{yun.log.fileName}}
    +
    文件路径:{{yun.log.filePath}}
    +
    +
    +
    +
    +
    + + + +``` + +## 参考 + +1. Spring 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-multipart-file-upload-configuration +2. 七牛云官方文档:https://developer.qiniu.com/kodo/sdk/1239/java#5 + diff --git a/demo-upload/pom.xml b/demo-upload/pom.xml new file mode 100644 index 0000000..7f0792d --- /dev/null +++ b/demo-upload/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + demo-upload + 1.0.0-SNAPSHOT + jar + + demo-upload + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + com.qiniu + qiniu-java-sdk + [7.2.0, 7.2.99] + + + + + demo-upload + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java b/demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java new file mode 100644 index 0000000..ff9fba8 --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.upload; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-20 21:23 + */ +@SpringBootApplication +public class SpringBootDemoUploadApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoUploadApplication.class, args); + } +} diff --git a/demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java b/demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java new file mode 100644 index 0000000..f7a835e --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java @@ -0,0 +1,100 @@ +package com.xkcoding.upload.config; + +import com.qiniu.common.Zone; +import com.qiniu.storage.BucketManager; +import com.qiniu.storage.UploadManager; +import com.qiniu.util.Auth; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; +import org.springframework.web.servlet.DispatcherServlet; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.Servlet; + +/** + *

    + * 上传配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-23 14:09 + */ +@Configuration +@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class}) +@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) +@EnableConfigurationProperties(MultipartProperties.class) +public class UploadConfig { + @Value("${qiniu.accessKey}") + private String accessKey; + + @Value("${qiniu.secretKey}") + private String secretKey; + + private final MultipartProperties multipartProperties; + + @Autowired + public UploadConfig(MultipartProperties multipartProperties) { + this.multipartProperties = multipartProperties; + } + + /** + * 上传配置 + */ + @Bean + @ConditionalOnMissingBean + public MultipartConfigElement multipartConfigElement() { + return this.multipartProperties.createMultipartConfig(); + } + + /** + * 注册解析器 + */ + @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) + @ConditionalOnMissingBean(MultipartResolver.class) + public StandardServletMultipartResolver multipartResolver() { + StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); + multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); + return multipartResolver; + } + + /** + * 华东机房 + */ + @Bean + public com.qiniu.storage.Configuration qiniuConfig() { + return new com.qiniu.storage.Configuration(Zone.zone0()); + } + + /** + * 构建一个七牛上传工具实例 + */ + @Bean + public UploadManager uploadManager() { + return new UploadManager(qiniuConfig()); + } + + /** + * 认证信息实例 + */ + @Bean + public Auth auth() { + return Auth.create(accessKey, secretKey); + } + + /** + * 构建七牛空间管理实例 + */ + @Bean + public BucketManager bucketManager() { + return new BucketManager(auth(), qiniuConfig()); + } +} diff --git a/demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java b/demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java new file mode 100644 index 0000000..c1fbe33 --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java @@ -0,0 +1,20 @@ +package com.xkcoding.upload.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + *

    + * 首页Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-20 21:22 + */ +@Controller +public class IndexController { + @GetMapping("") + public String index() { + return "index"; + } +} diff --git a/demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java b/demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java new file mode 100644 index 0000000..bf77666 --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java @@ -0,0 +1,101 @@ +package com.xkcoding.upload.controller; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.qiniu.http.Response; +import com.xkcoding.upload.service.IQiNiuService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; + +/** + *

    + * 文件上传 Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-06 16:33 + */ +@RestController +@Slf4j +@RequestMapping("/upload") +public class UploadController { + @Value("${spring.servlet.multipart.location}") + private String fileTempPath; + + @Value("${qiniu.prefix}") + private String prefix; + + private final IQiNiuService qiNiuService; + + @Autowired + public UploadController(IQiNiuService qiNiuService) { + this.qiNiuService = qiNiuService; + } + + @PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Dict local(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return Dict.create().set("code", 400).set("message", "文件内容为空"); + } + String fileName = file.getOriginalFilename(); + String rawFileName = StrUtil.subBefore(fileName, ".", true); + String fileType = StrUtil.subAfter(fileName, ".", true); + String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; + try { + file.transferTo(new File(localFilePath)); + } catch (IOException e) { + log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + + log.info("【文件上传至本地】绝对路径:{}", localFilePath); + return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath)); + } + + @PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Dict yun(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return Dict.create().set("code", 400).set("message", "文件内容为空"); + } + String fileName = file.getOriginalFilename(); + String rawFileName = StrUtil.subBefore(fileName, ".", true); + String fileType = StrUtil.subAfter(fileName, ".", true); + String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; + try { + file.transferTo(new File(localFilePath)); + Response response = qiNiuService.uploadFile(new File(localFilePath)); + if (response.isOK()) { + JSONObject jsonObject = JSONUtil.parseObj(response.bodyString()); + + String yunFileName = jsonObject.getStr("key"); + String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName; + + FileUtil.del(new File(localFilePath)); + + log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath); + return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath)); + } else { + log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response)); + FileUtil.del(new File(localFilePath)); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + } catch (IOException e) { + log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath); + return Dict.create().set("code", 500).set("message", "文件上传失败"); + } + } +} diff --git a/demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java b/demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java new file mode 100644 index 0000000..b0d11d4 --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java @@ -0,0 +1,25 @@ +package com.xkcoding.upload.service; + +import com.qiniu.common.QiniuException; +import com.qiniu.http.Response; + +import java.io.File; + +/** + *

    + * 七牛云上传Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-06 17:21 + */ +public interface IQiNiuService { + /** + * 七牛云上传文件 + * + * @param file 文件 + * @return 七牛上传Response + * @throws QiniuException 七牛异常 + */ + Response uploadFile(File file) throws QiniuException; +} diff --git a/demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java b/demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java new file mode 100644 index 0000000..5b08523 --- /dev/null +++ b/demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java @@ -0,0 +1,75 @@ +package com.xkcoding.upload.service.impl; + +import com.qiniu.common.QiniuException; +import com.qiniu.http.Response; +import com.qiniu.storage.UploadManager; +import com.qiniu.util.Auth; +import com.qiniu.util.StringMap; +import com.xkcoding.upload.service.IQiNiuService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.File; + +/** + *

    + * 七牛云上传Service + *

    + * + * @author yangkai.shen + * @date Created in 2018-11-06 17:22 + */ +@Service +@Slf4j +public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { + private final UploadManager uploadManager; + + private final Auth auth; + + @Value("${qiniu.bucket}") + private String bucket; + + private StringMap putPolicy; + + @Autowired + public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) { + this.uploadManager = uploadManager; + this.auth = auth; + } + + /** + * 七牛云上传文件 + * + * @param file 文件 + * @return 七牛上传Response + * @throws QiniuException 七牛异常 + */ + @Override + public Response uploadFile(File file) throws QiniuException { + Response response = this.uploadManager.put(file, file.getName(), getUploadToken()); + int retry = 0; + while (response.needRetry() && retry < 3) { + response = this.uploadManager.put(file, file.getName(), getUploadToken()); + retry++; + } + return response; + } + + @Override + public void afterPropertiesSet() { + this.putPolicy = new StringMap(); + putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}"); + } + + /** + * 获取上传凭证 + * + * @return 上传凭证 + */ + private String getUploadToken() { + return this.auth.uploadToken(bucket, null, 3600, putPolicy); + } +} diff --git a/spring-boot-demo-upload/src/main/resources/application.yml b/demo-upload/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-upload/src/main/resources/application.yml rename to demo-upload/src/main/resources/application.yml diff --git a/spring-boot-demo-upload/src/main/resources/templates/index.html b/demo-upload/src/main/resources/templates/index.html similarity index 100% rename from spring-boot-demo-upload/src/main/resources/templates/index.html rename to demo-upload/src/main/resources/templates/index.html diff --git a/demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java b/demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java new file mode 100644 index 0000000..44ce0e8 --- /dev/null +++ b/demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.upload; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoUploadApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-urule/.gitignore b/demo-ureport2/.gitignore similarity index 100% rename from spring-boot-demo-urule/.gitignore rename to demo-ureport2/.gitignore diff --git a/demo-ureport2/README.md b/demo-ureport2/README.md new file mode 100644 index 0000000..02151c6 --- /dev/null +++ b/demo-ureport2/README.md @@ -0,0 +1,260 @@ +# spring-boot-demo-ureport2 + +> 本 demo 主要演示了 Spring Boot 项目如何快速集成 ureport2 实现任意复杂的中国式报表功能。 + +UReport2 是一款基于架构在 Spring 之上纯 Java 的高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。 在 UReport2 中,提供了全新的基于网页的报表设计器,可以在 Chrome、Firefox、Edge 等各种主流浏览器运行(IE 浏览器除外)。使用 UReport2,打开浏览器即可完成各种复杂报表的设计制作。 + +## 1. 主要代码 + +因为官方没有提供一个 starter 包,需要自己集成,这里使用 [pig](https://github.com/pig-mesh/pig) 作者 [冷冷同学](https://github.com/lltx) 开发的 starter 偷懒实现,这个 starter 不仅支持单机环境的配置,同时支持集群环境。 + +### 1.1. 单机使用 + +#### 1.1.1. `pom.xml` 新增依赖 + +```xml + + com.pig4cloud.plugin + ureport-spring-boot-starter + 0.0.1 + +``` + +#### 1.1.2. `application.yml` 修改配置文件 + +```yaml +server: + port: 8080 + servlet: + context-path: /demo +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver +ureport: + debug: false + disableFileProvider: false + disableHttpSessionReportCache: true + # 单机模式,本地路径需要提前创建 + fileStoreDir: '/Users/yk.shen/Desktop/ureport2' +``` +#### 1.1.3. 新增一个内部数据源 + +```java +@Component +public class InnerDatasource implements BuildinDatasource { + @Autowired + private DataSource datasource; + + @Override + public String name() { + return "内部数据源"; + } + + @SneakyThrows + @Override + public Connection getConnection() { + return datasource.getConnection(); + } +} +``` + +#### 1.1.4. 使用 `doc/sql/t_user_ureport2.sql` 初始化数据 + +```mysql +DROP TABLE IF EXISTS `t_user_ureport2`; +CREATE TABLE `t_user_ureport2` ( + `id` bigint(13) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '姓名', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', + `status` tinyint(4) NOT NULL COMMENT '是否禁用', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +BEGIN; +INSERT INTO `t_user_ureport2` VALUES (1, '测试人员 1', '2020-10-22 09:01:58', 1); +INSERT INTO `t_user_ureport2` VALUES (2, '测试人员 2', '2020-10-22 09:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (3, '测试人员 3', '2020-10-23 03:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (4, '测试人员 4', '2020-10-23 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (5, '测试人员 5', '2020-10-23 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (6, '测试人员 6', '2020-10-24 11:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (7, '测试人员 7', '2020-10-24 20:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (8, '测试人员 8', '2020-10-25 08:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (9, '测试人员 9', '2020-10-25 09:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (10, '测试人员 10', '2020-10-25 13:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (11, '测试人员 11', '2020-10-26 21:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (12, '测试人员 12', '2020-10-26 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (13, '测试人员 13', '2020-10-26 23:02:00', 1); +COMMIT; +``` + +#### 1.1.5. 访问报表设计器 + +http://127.0.0.1:8080/demo/ureport/designer + +![报表设计页](http://static.xkcoding.com/spring-boot-demo/ureport2/035330.png) + +#### 1.1.6. 开始设计 + +##### 1.1.6.1. 选择数据源 + +这里就需要使用到上面步骤 1.1.3 创建的内部数据源如图 + +![选择数据源](http://static.xkcoding.com/spring-boot-demo/ureport2/040032.png) + +选择数据源 + +![选择数据源](http://static.xkcoding.com/spring-boot-demo/ureport2/040117.png) + +此时列表里就会出现数据源 + +![数据源列表](http://static.xkcoding.com/spring-boot-demo/ureport2/040237.png) + +##### 1.1.6.2. 选择数据集 + +在刚才选中的数据源右键,选择添加数据集 + +![选中数据源右键](http://static.xkcoding.com/spring-boot-demo/ureport2/063315.png) + +这里选择上面步骤 1.1.4 中初始化的用户表 + +![创建用户报表](http://static.xkcoding.com/spring-boot-demo/ureport2/063845.png) + +预览数据看一下 + +![预览数据集数据](http://static.xkcoding.com/spring-boot-demo/ureport2/063955.png) + +点击确定,保存数据集 + +![保存数据集](http://static.xkcoding.com/spring-boot-demo/ureport2/064049.png) + +##### 1.1.6.3. 报表设计 + +创建报表表头的位置 + +![合并单元格](http://static.xkcoding.com/spring-boot-demo/ureport2/064425.png) + +表头内容 + +![image-20201124144752390](http://static.xkcoding.com/spring-boot-demo/ureport2/064752.png) + +操作完成之后,长这样~ + +![表头美化](http://static.xkcoding.com/spring-boot-demo/ureport2/064916.png) + + + +然后设置数据的标题行,跟表头设置一样,效果如下图 + +![数据的标题行](http://static.xkcoding.com/spring-boot-demo/ureport2/065125.png) + +接下来设置数据 + +![id字段配置](http://static.xkcoding.com/spring-boot-demo/ureport2/065658.png) + +其他字段同理,完成之后如下 + +![数据配置](http://static.xkcoding.com/spring-boot-demo/ureport2/070440.png) + +此时你可以尝试预览一下数据了 + +![预览数据](http://static.xkcoding.com/spring-boot-demo/ureport2/070634.png) + +![预览数据](http://static.xkcoding.com/spring-boot-demo/ureport2/070813.png) + +关掉,稍微美化一下 + +![美化后的预览数据](http://static.xkcoding.com/spring-boot-demo/ureport2/070910.png) + +此时数据虽然正常显示了,但是「是否可用」这一列显示0/1 是否可以支持自定义呢? + +![映射数据集](http://static.xkcoding.com/spring-boot-demo/ureport2/071352.png) + +再次预览一下 + +![字典映射预览数据](http://static.xkcoding.com/spring-boot-demo/ureport2/071428.png) + +顺带再把创建时间的数据格式也改一下 + +![时间格式修改](http://static.xkcoding.com/spring-boot-demo/ureport2/072725.png) + +修改后,预览数据如下 + +![预览数据](http://static.xkcoding.com/spring-boot-demo/ureport2/072753.png) + +##### 1.1.6.4. 保存报表设计文件 + +![image-20201124153244035](http://static.xkcoding.com/spring-boot-demo/ureport2/073244.png) + +![保存](http://static.xkcoding.com/spring-boot-demo/ureport2/074228.png) + +点击保存之后,你本地在 `application.yml` 文件中配置的地址就会出现一个 `demo.ureport.xml` 文件 + +下次可以直接通过 http://localhost:8080/demo/ureport/preview?_u=file:demo.ureport.xml 这个地址预览报表了 + +##### 1.1.6.5. 增加报表查询条件 + +还记得我们上面新增数据集的时候,加的条件吗?现在用起来 + +![查询表单设计器](http://static.xkcoding.com/spring-boot-demo/ureport2/074641.png) + +查询表单设计 + +![拖动元素设计表单查询](http://static.xkcoding.com/spring-boot-demo/ureport2/074936.png) + +配置查询参数 + +![完善查询表单](http://static.xkcoding.com/spring-boot-demo/ureport2/075248.png) + +美化按钮 + +![按钮样式美化](http://static.xkcoding.com/spring-boot-demo/ureport2/075410.png) + +在预览一下~ + +![预览数据-查询条件](http://static.xkcoding.com/spring-boot-demo/ureport2/075640.png) + +### 1.2. 集群使用 + +如上文设计好的模板是保存在服务本机的,在集群环境中需要使用统一的文件系统存储。 + +#### 1.2.1. 新增依赖 + +```xml + + com.pig4cloud.plugin + oss-spring-boot-starter + 0.0.3 + +``` + +#### 1.2.2. 仅需配置云存储相关参数, 演示为minio + +```yaml +oss: + access-key: lengleng + secret-key: lengleng + bucket-name: lengleng + endpoint: http://minio.pig4cloud.com +``` + +> 注意:这里使用的是冷冷提供的公共 minio,请勿乱用,也不保证数据的可靠性,建议小伙伴自建一个minio,或者使用阿里云 oss + +## 2. 坑 + +Ureport2 最新版本是 `2.2.9`,挺久没更新了,存在一个坑:在报表设计页打开一个已存在的报表设计文件时,可能会出现无法预览的情况,参考 ISSUE:https://github.com/youseries/ureport/issues/393 + +注意:该可能性出现在报表设计文件中使用了条件属性的情况下,修复方法就是打开文件之后,重新配置条件属性,此处是坑,小伙伴使用时注意下就好,最好的方法就是避免使用条件属性。 + +## 3. 感谢 + +再次感谢 [@冷冷](https://github.com/lltx) 提供的 starter 及 PR,因个人操作失误,PR 未被合并,抱歉~ + +## 4. 参考 + +- [ureport2 使用文档](https://www.w3cschool.cn/ureport) +- [ureport-spring-boot-starter](https://github.com/pig-mesh/ureport-spring-boot-starter) UReport2 的 spring boot 封装 +- [oss-spring-boot-starter](https://github.com/pig-mesh/oss-spring-boot-starter) 兼容所有 S3 协议的分布式文件存储系统 + diff --git a/demo-ureport2/doc/sql/t_user_ureport2.sql b/demo-ureport2/doc/sql/t_user_ureport2.sql new file mode 100644 index 0000000..f328f77 --- /dev/null +++ b/demo-ureport2/doc/sql/t_user_ureport2.sql @@ -0,0 +1,51 @@ +/* + Navicat Premium Data Transfer + + Source Server : dev + Source Server Type : MySQL + Source Server Version : 50732 + Source Host : localhost:3306 + Source Schema : spring-boot-demo + + Target Server Type : MySQL + Target Server Version : 50732 + File Encoding : 65001 + + Date: 26/10/2020 23:30:27 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_user_ureport2 +-- ---------------------------- +DROP TABLE IF EXISTS `t_user_ureport2`; +CREATE TABLE `t_user_ureport2` ( + `id` bigint(13) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '姓名', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', + `status` tinyint(4) NOT NULL COMMENT '是否禁用', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +-- ---------------------------- +-- Records of t_user_ureport2 +-- ---------------------------- +BEGIN; +INSERT INTO `t_user_ureport2` VALUES (1, '测试人员 1', '2020-10-22 09:01:58', 1); +INSERT INTO `t_user_ureport2` VALUES (2, '测试人员 2', '2020-10-22 09:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (3, '测试人员 3', '2020-10-23 03:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (4, '测试人员 4', '2020-10-23 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (5, '测试人员 5', '2020-10-23 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (6, '测试人员 6', '2020-10-24 11:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (7, '测试人员 7', '2020-10-24 20:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (8, '测试人员 8', '2020-10-25 08:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (9, '测试人员 9', '2020-10-25 09:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (10, '测试人员 10', '2020-10-25 13:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (11, '测试人员 11', '2020-10-26 21:02:00', 0); +INSERT INTO `t_user_ureport2` VALUES (12, '测试人员 12', '2020-10-26 23:02:00', 1); +INSERT INTO `t_user_ureport2` VALUES (13, '测试人员 13', '2020-10-26 23:02:00', 1); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/demo-ureport2/doc/ureport2/user_inner_datasource.ureport.xml b/demo-ureport2/doc/ureport2/user_inner_datasource.ureport.xml new file mode 100644 index 0000000..4fabe07 --- /dev/null +++ b/demo-ureport2/doc/ureport2/user_inner_datasource.ureport.xml @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/demo-ureport2/pom.xml b/demo-ureport2/pom.xml new file mode 100644 index 0000000..c92864f --- /dev/null +++ b/demo-ureport2/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + demo-ureport2 + 1.0.0-SNAPSHOT + jar + + demo-ureport2 + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + + + + + com.pig4cloud.plugin + ureport-spring-boot-starter + 0.0.1 + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-ureport2 + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java b/demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java new file mode 100644 index 0000000..f16fd40 --- /dev/null +++ b/demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java @@ -0,0 +1,22 @@ +package com.xkcoding.ureport2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-02-26 23:56 + */ +@SpringBootApplication +public class SpringBootDemoUreport2Application { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoUreport2Application.class, args); + } + +} + diff --git a/demo-ureport2/src/main/java/com/xkcoding/ureport2/config/InnerDatasource.java b/demo-ureport2/src/main/java/com/xkcoding/ureport2/config/InnerDatasource.java new file mode 100644 index 0000000..6ecf0fa --- /dev/null +++ b/demo-ureport2/src/main/java/com/xkcoding/ureport2/config/InnerDatasource.java @@ -0,0 +1,34 @@ +package com.xkcoding.ureport2.config; + +import com.bstek.ureport.definition.datasource.BuildinDatasource; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.sql.Connection; + +/** + *

    + * 内部数据源 + *

    + * + * @author yangkai.shen + * @date Created in 2020-10-26 22:32 + */ +@Component +public class InnerDatasource implements BuildinDatasource { + @Autowired + private DataSource datasource; + + @Override + public String name() { + return "内部数据源"; + } + + @SneakyThrows + @Override + public Connection getConnection() { + return datasource.getConnection(); + } +} diff --git a/demo-ureport2/src/main/resources/application.yml b/demo-ureport2/src/main/resources/application.yml new file mode 100644 index 0000000..1246b55 --- /dev/null +++ b/demo-ureport2/src/main/resources/application.yml @@ -0,0 +1,21 @@ +server: + port: 8080 + servlet: + context-path: /demo +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver +ureport: + debug: false + disableFileProvider: false + disableHttpSessionReportCache: true + # 单机模式,本地路径需要提前创建 + fileStoreDir: '/Users/yk.shen/Desktop/ureport2' +#oss: +# access-key: lengleng +# secret-key: lengleng +# bucket-name: lengleng +# endpoint: http://minio.pig4cloud.com diff --git a/spring-boot-demo-ureport2/src/test/java/com/xkcoding/ureport2/SpringBootDemoUreport2ApplicationTests.java b/demo-ureport2/src/test/java/com/xkcoding/ureport2/SpringBootDemoUreport2ApplicationTests.java similarity index 100% rename from spring-boot-demo-ureport2/src/test/java/com/xkcoding/ureport2/SpringBootDemoUreport2ApplicationTests.java rename to demo-ureport2/src/test/java/com/xkcoding/ureport2/SpringBootDemoUreport2ApplicationTests.java diff --git a/spring-boot-demo-war/.gitignore b/demo-urule/.gitignore similarity index 100% rename from spring-boot-demo-war/.gitignore rename to demo-urule/.gitignore diff --git a/demo-urule/pom.xml b/demo-urule/pom.xml new file mode 100644 index 0000000..416b7b1 --- /dev/null +++ b/demo-urule/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + demo-urule + 1.0.0-SNAPSHOT + jar + + demo-urule + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-urule + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java b/demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java new file mode 100644 index 0000000..3ff9b9f --- /dev/null +++ b/demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.urule; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2019-02-25 22:46 + */ +@SpringBootApplication +public class SpringBootDemoUruleApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoUruleApplication.class, args); + } + +} + diff --git a/spring-boot-demo-urule/src/main/resources/application.properties b/demo-urule/src/main/resources/application.properties similarity index 100% rename from spring-boot-demo-urule/src/main/resources/application.properties rename to demo-urule/src/main/resources/application.properties diff --git a/spring-boot-demo-urule/src/test/java/com/xkcoding/urule/SpringBootDemoUruleApplicationTests.java b/demo-urule/src/test/java/com/xkcoding/urule/SpringBootDemoUruleApplicationTests.java similarity index 100% rename from spring-boot-demo-urule/src/test/java/com/xkcoding/urule/SpringBootDemoUruleApplicationTests.java rename to demo-urule/src/test/java/com/xkcoding/urule/SpringBootDemoUruleApplicationTests.java diff --git a/spring-boot-demo-websocket-socketio/.gitignore b/demo-war/.gitignore similarity index 100% rename from spring-boot-demo-websocket-socketio/.gitignore rename to demo-war/.gitignore diff --git a/demo-war/README.md b/demo-war/README.md new file mode 100644 index 0000000..6f1f182 --- /dev/null +++ b/demo-war/README.md @@ -0,0 +1,97 @@ +# spring-boot-demo-war + +> 本 demo 主要演示了如何将 Spring Boot 项目打包成传统的 war 包程序。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-war + 1.0.0-SNAPSHOT + + war + + spring-boot-demo-war + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + spring-boot-demo-war + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## SpringBootDemoWarApplication.java + +```java +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-30 19:37 + */ +@SpringBootApplication +public class SpringBootDemoWarApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoWarApplication.class, args); + } + + /** + * 若需要打成 war 包,则需要写一个类继承 {@link SpringBootServletInitializer} 并重写 {@link SpringBootServletInitializer#configure(SpringApplicationBuilder)} + */ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(SpringBootDemoWarApplication.class); + } +} +``` + +## 参考 + +https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-create-a-deployable-war-file + diff --git a/demo-war/pom.xml b/demo-war/pom.xml new file mode 100644 index 0000000..e778242 --- /dev/null +++ b/demo-war/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + demo-war + 1.0.0-SNAPSHOT + + war + + demo-war + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + demo-war + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + + diff --git a/demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java b/demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java new file mode 100644 index 0000000..f1da4cd --- /dev/null +++ b/demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java @@ -0,0 +1,30 @@ +package com.xkcoding.war; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-30 19:37 + */ +@SpringBootApplication +public class SpringBootDemoWarApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoWarApplication.class, args); + } + + /** + * 若需要打成 war 包,则需要写一个类继承 {@link SpringBootServletInitializer} 并重写 {@link SpringBootServletInitializer#configure(SpringApplicationBuilder)} + */ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(SpringBootDemoWarApplication.class); + } +} diff --git a/spring-boot-demo-websocket/src/main/resources/application.yml b/demo-war/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-websocket/src/main/resources/application.yml rename to demo-war/src/main/resources/application.yml diff --git a/demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java b/demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java new file mode 100644 index 0000000..300b377 --- /dev/null +++ b/demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java @@ -0,0 +1,16 @@ +package com.xkcoding.war; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoWarApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-demo-websocket/.gitignore b/demo-websocket-socketio/.gitignore similarity index 100% rename from spring-boot-demo-websocket/.gitignore rename to demo-websocket-socketio/.gitignore diff --git a/demo-websocket-socketio/README.md b/demo-websocket-socketio/README.md new file mode 100644 index 0000000..60d43cd --- /dev/null +++ b/demo-websocket-socketio/README.md @@ -0,0 +1,319 @@ +# spring-boot-demo-websocket-socketio + +> 此 demo 主要演示了 Spring Boot 如何使用 `netty-socketio` 集成 WebSocket,实现一个简单的聊天室。 + +## 1. 代码 + +### 1.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-websocket-socketio + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-websocket-socketio + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.7.16 + + + + + com.corundumstudio.socketio + netty-socketio + ${netty-socketio.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-websocket-socketio + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 1.2. ServerConfig.java + +> websocket服务器配置,包括服务器IP、端口信息、以及连接认证等配置 + +```java +/** + *

    + * websocket服务器配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 16:42 + */ +@Configuration +@EnableConfigurationProperties({WsConfig.class}) +public class ServerConfig { + + @Bean + public SocketIOServer server(WsConfig wsConfig) { + com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); + config.setHostname(wsConfig.getHost()); + config.setPort(wsConfig.getPort()); + + //这个listener可以用来进行身份验证 + config.setAuthorizationListener(data -> { + // http://localhost:8081?token=xxxxxxx + // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证 + String token = data.getSingleUrlParam("token"); + // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil + // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件 + return StrUtil.isNotBlank(token); + }); + + return new SocketIOServer(config); + } + + /** + * Spring 扫描自定义注解 + */ + @Bean + public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) { + return new SpringAnnotationScanner(server); + } +} +``` + +### 1.3. MessageEventHandler.java + +> 核心事件处理类,主要处理客户端发起的消息事件,以及主动往客户端发起事件 + +```java +/** + *

    + * 消息事件处理 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 18:57 + */ +@Component +@Slf4j +public class MessageEventHandler { + @Autowired + private SocketIOServer server; + + @Autowired + private DbTemplate dbTemplate; + + /** + * 添加connect事件,当客户端发起连接时调用 + * + * @param client 客户端对象 + */ + @OnConnect + public void onConnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.save(userId, sessionId); + log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId); + } else { + log.error("客户端为空"); + } + } + + /** + * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息 + * + * @param client 客户端对象 + */ + @OnDisconnect + public void onDisconnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.deleteByUserId(userId); + log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId); + client.disconnect(); + } else { + log.error("客户端为空"); + } + } + + /** + * 加入群聊 + * + * @param client 客户端 + * @param request 请求 + * @param data 群聊 + */ + @OnEvent(value = Event.JOIN) + public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) { + log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId()); + client.joinRoom(data.getGroupId()); + + server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data); + } + + + @OnEvent(value = Event.CHAT) + public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) { + Optional toUser = dbTemplate.findByUserId(data.getToUid()); + if (toUser.isPresent()) { + log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage()); + sendToSingle(toUser.get(), data); + client.sendEvent(Event.CHAT_RECEIVED, "发送成功"); + } else { + client.sendEvent(Event.CHAT_REFUSED, "发送失败,对方不想理你"); + } + } + + @OnEvent(value = Event.GROUP) + public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) { + Collection clients = server.getRoomOperations(data.getGroupId()).getClients(); + + boolean inGroup = false; + for (SocketIOClient socketIOClient : clients) { + if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) { + inGroup = true; + break; + } + } + if (inGroup) { + log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage()); + sendToGroup(data); + } else { + request.sendAckData("请先加群!"); + } + } + + /** + * 单聊 + */ + public void sendToSingle(UUID sessionId, SingleMessageRequest message) { + server.getClient(sessionId).sendEvent(Event.CHAT, message); + } + + /** + * 广播 + */ + public void sendToBroadcast(BroadcastMessageRequest message) { + log.info("系统紧急广播一条通知:{}", message.getMessage()); + for (UUID clientId : dbTemplate.findAll()) { + if (server.getClient(clientId) == null) { + continue; + } + server.getClient(clientId).sendEvent(Event.BROADCAST, message); + } + } + + /** + * 群聊 + */ + public void sendToGroup(GroupMessageRequest message) { + server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message); + } +} +``` + +### 1.4. ServerRunner.java + +> websocket 服务器启动类 + +```java +/** + *

    + * websocket服务器启动 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 17:07 + */ +@Component +@Slf4j +public class ServerRunner implements CommandLineRunner { + @Autowired + private SocketIOServer server; + + @Override + public void run(String... args) { + server.start(); + log.info("websocket 服务器启动成功。。。"); + } +} +``` + +## 2. 运行方式 + +1. 启动 `SpringBootDemoWebsocketSocketioApplication.java` +2. 使用不同的浏览器,访问 http://localhost:8080/demo/index.html + +## 3. 运行效果 + +**浏览器1:**![image-20181219152318079](http://static.xkcoding.com/spring-boot-demo/websocket/socketio/064155.jpg) + +**浏览器2:**![image-20181219152330156](http://static.xkcoding.com/spring-boot-demo/websocket/socketio/064154.jpg) + +## 4. 参考 + +### 4.1. 后端 + +1. Netty-socketio 官方仓库:https://github.com/mrniko/netty-socketio +2. SpringBoot系列 - 集成SocketIO实时通信:https://www.xncoding.com/2017/07/16/spring/sb-socketio.html +3. Spring Boot 集成 socket.io 后端实现消息实时通信:http://alexpdh.com/2017/09/03/springboot-socketio/ +4. Spring Boot实战之netty-socketio实现简单聊天室:http://blog.csdn.net/sun_t89/article/details/52060946 + +### 4.2. 前端 + +1. socket.io 官网:https://socket.io/ +2. axios.js 用法:https://github.com/axios/axios#example diff --git a/demo-websocket-socketio/pom.xml b/demo-websocket-socketio/pom.xml new file mode 100644 index 0000000..28a4442 --- /dev/null +++ b/demo-websocket-socketio/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + demo-websocket-socketio + 1.0.0-SNAPSHOT + jar + + demo-websocket-socketio + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 1.7.16 + + + + + com.corundumstudio.socketio + netty-socketio + ${netty-socketio.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + demo-websocket-socketio + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java new file mode 100644 index 0000000..2d1c7fa --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java @@ -0,0 +1,20 @@ +package com.xkcoding.websocket.socketio; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-12 13:59 + */ +@SpringBootApplication +public class SpringBootDemoWebsocketSocketioApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoWebsocketSocketioApplication.class, args); + } +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java new file mode 100644 index 0000000..8f24558 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java @@ -0,0 +1,64 @@ +package com.xkcoding.websocket.socketio.config; + +import cn.hutool.core.collection.CollUtil; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

    + * 模拟数据库 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 19:12 + */ +@Component +public class DbTemplate { + /** + * 模拟数据库存储 user_id <-> session_id 的关系 + */ + public static final ConcurrentHashMap DB = new ConcurrentHashMap<>(); + + /** + * 获取所有SessionId + * + * @return SessionId列表 + */ + public List findAll() { + return CollUtil.newArrayList(DB.values()); + } + + /** + * 根据UserId查询SessionId + * + * @param userId 用户id + * @return SessionId + */ + public Optional findByUserId(String userId) { + return Optional.ofNullable(DB.get(userId)); + } + + /** + * 保存/更新 user_id <-> session_id 的关系 + * + * @param userId 用户id + * @param sessionId SessionId + */ + public void save(String userId, UUID sessionId) { + DB.put(userId, sessionId); + } + + /** + * 删除 user_id <-> session_id 的关系 + * + * @param userId 用户id + */ + public void deleteByUserId(String userId) { + DB.remove(userId); + } + +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java new file mode 100644 index 0000000..e7ecb2c --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java @@ -0,0 +1,32 @@ +package com.xkcoding.websocket.socketio.config; + +/** + *

    + * 事件常量 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 19:36 + */ +public interface Event { + /** + * 聊天事件 + */ + String CHAT = "chat"; + + /** + * 广播消息 + */ + String BROADCAST = "broadcast"; + + /** + * 群聊 + */ + String GROUP = "group"; + + /** + * 加入群聊 + */ + String JOIN = "join"; + +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java new file mode 100644 index 0000000..15985a3 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java @@ -0,0 +1,48 @@ +package com.xkcoding.websocket.socketio.config; + +import cn.hutool.core.util.StrUtil; +import com.corundumstudio.socketio.SocketIOServer; +import com.corundumstudio.socketio.annotation.SpringAnnotationScanner; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

    + * websocket服务器配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 16:42 + */ +@Configuration +@EnableConfigurationProperties({WsConfig.class}) +public class ServerConfig { + + @Bean + public SocketIOServer server(WsConfig wsConfig) { + com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); + config.setHostname(wsConfig.getHost()); + config.setPort(wsConfig.getPort()); + + //这个listener可以用来进行身份验证 + config.setAuthorizationListener(data -> { + // http://localhost:8081?token=xxxxxxx + // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证 + String token = data.getSingleUrlParam("token"); + // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil + // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件 + return StrUtil.isNotBlank(token); + }); + + return new SocketIOServer(config); + } + + /** + * Spring 扫描自定义注解 + */ + @Bean + public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) { + return new SpringAnnotationScanner(server); + } +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java new file mode 100644 index 0000000..1077fae --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java @@ -0,0 +1,26 @@ +package com.xkcoding.websocket.socketio.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

    + * WebSocket配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 16:41 + */ +@ConfigurationProperties(prefix = "ws.server") +@Data +public class WsConfig { + /** + * 端口号 + */ + private Integer port; + + /** + * host + */ + private String host; +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java new file mode 100644 index 0000000..a64c638 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java @@ -0,0 +1,63 @@ +package com.xkcoding.websocket.socketio.controller; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.xkcoding.websocket.socketio.handler.MessageEventHandler; +import com.xkcoding.websocket.socketio.payload.BroadcastMessageRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.lang.reflect.Field; + +/** + *

    + * 消息发送Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 19:50 + */ +@RestController +@RequestMapping("/send") +@Slf4j +public class MessageController { + @Autowired + private MessageEventHandler messageHandler; + + @PostMapping("/broadcast") + public Dict broadcast(@RequestBody BroadcastMessageRequest message) { + if (isBlank(message)) { + return Dict.create().set("flag", false).set("code", 400).set("message", "参数为空"); + } + messageHandler.sendToBroadcast(message); + return Dict.create().set("flag", true).set("code", 200).set("message", "发送成功"); + } + + /** + * 判断Bean是否为空对象或者空白字符串,空对象表示本身为null或者所有属性都为null + * + * @param bean Bean对象 + * @return 是否为空,true - 空 / false - 非空 + */ + private boolean isBlank(Object bean) { + if (null != bean) { + for (Field field : ReflectUtil.getFields(bean.getClass())) { + Object fieldValue = ReflectUtil.getFieldValue(bean, field); + if (null != fieldValue) { + if (fieldValue instanceof String && StrUtil.isNotBlank((String) fieldValue)) { + return false; + } else if (!(fieldValue instanceof String)) { + return false; + } + } + } + } + return true; + } + +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java new file mode 100644 index 0000000..9ae36b6 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java @@ -0,0 +1,156 @@ +package com.xkcoding.websocket.socketio.handler; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.corundumstudio.socketio.AckRequest; +import com.corundumstudio.socketio.SocketIOClient; +import com.corundumstudio.socketio.SocketIOServer; +import com.corundumstudio.socketio.annotation.OnConnect; +import com.corundumstudio.socketio.annotation.OnDisconnect; +import com.corundumstudio.socketio.annotation.OnEvent; +import com.xkcoding.websocket.socketio.config.DbTemplate; +import com.xkcoding.websocket.socketio.config.Event; +import com.xkcoding.websocket.socketio.payload.BroadcastMessageRequest; +import com.xkcoding.websocket.socketio.payload.GroupMessageRequest; +import com.xkcoding.websocket.socketio.payload.JoinRequest; +import com.xkcoding.websocket.socketio.payload.SingleMessageRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +/** + *

    + * 消息事件处理 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 18:57 + */ +@Component +@Slf4j +public class MessageEventHandler { + @Autowired + private SocketIOServer server; + + @Autowired + private DbTemplate dbTemplate; + + /** + * 添加connect事件,当客户端发起连接时调用 + * + * @param client 客户端对象 + */ + @OnConnect + public void onConnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.save(userId, sessionId); + log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId); + } else { + log.error("客户端为空"); + } + } + + /** + * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息 + * + * @param client 客户端对象 + */ + @OnDisconnect + public void onDisconnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.deleteByUserId(userId); + log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId); + client.disconnect(); + } else { + log.error("客户端为空"); + } + } + + /** + * 加入群聊 + * + * @param client 客户端 + * @param request 请求 + * @param data 群聊 + */ + @OnEvent(value = Event.JOIN) + public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) { + log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId()); + client.joinRoom(data.getGroupId()); + + server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data); + } + + + @OnEvent(value = Event.CHAT) + public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) { + Optional toUser = dbTemplate.findByUserId(data.getToUid()); + if (toUser.isPresent()) { + log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage()); + sendToSingle(toUser.get(), data); + request.sendAckData(Dict.create().set("flag", true).set("message", "发送成功")); + } else { + request.sendAckData(Dict.create().set("flag", false).set("message", "发送失败,对方不想理你(" + data.getToUid() + "不在线)")); + } + } + + @OnEvent(value = Event.GROUP) + public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) { + Collection clients = server.getRoomOperations(data.getGroupId()).getClients(); + + boolean inGroup = false; + for (SocketIOClient socketIOClient : clients) { + if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) { + inGroup = true; + break; + } + } + if (inGroup) { + log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage()); + sendToGroup(data); + } else { + request.sendAckData("请先加群!"); + } + } + + /** + * 单聊 + */ + public void sendToSingle(UUID sessionId, SingleMessageRequest message) { + server.getClient(sessionId).sendEvent(Event.CHAT, message); + } + + /** + * 广播 + */ + public void sendToBroadcast(BroadcastMessageRequest message) { + log.info("系统紧急广播一条通知:{}", message.getMessage()); + for (UUID clientId : dbTemplate.findAll()) { + if (server.getClient(clientId) == null) { + continue; + } + server.getClient(clientId).sendEvent(Event.BROADCAST, message); + } + } + + /** + * 群聊 + */ + public void sendToGroup(GroupMessageRequest message) { + server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message); + } +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java new file mode 100644 index 0000000..23daf6e --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java @@ -0,0 +1,28 @@ +package com.xkcoding.websocket.socketio.init; + +import com.corundumstudio.socketio.SocketIOServer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +/** + *

    + * websocket服务器启动 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 17:07 + */ +@Component +@Slf4j +public class ServerRunner implements CommandLineRunner { + @Autowired + private SocketIOServer server; + + @Override + public void run(String... args) { + server.start(); + log.info("websocket 服务器启动成功。。。"); + } +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java new file mode 100644 index 0000000..47de011 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java @@ -0,0 +1,19 @@ +package com.xkcoding.websocket.socketio.payload; + +import lombok.Data; + +/** + *

    + * 广播消息载荷 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 20:01 + */ +@Data +public class BroadcastMessageRequest { + /** + * 消息内容 + */ + private String message; +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java new file mode 100644 index 0000000..67d7171 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java @@ -0,0 +1,29 @@ +package com.xkcoding.websocket.socketio.payload; + +import lombok.Data; + +/** + *

    + * 群聊消息载荷 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 16:59 + */ +@Data +public class GroupMessageRequest { + /** + * 消息发送方用户id + */ + private String fromUid; + + /** + * 群组id + */ + private String groupId; + + /** + * 消息内容 + */ + private String message; +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java new file mode 100644 index 0000000..d20d873 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java @@ -0,0 +1,24 @@ +package com.xkcoding.websocket.socketio.payload; + +import lombok.Data; + +/** + *

    + * 加群载荷 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-19 13:36 + */ +@Data +public class JoinRequest { + /** + * 用户id + */ + private String userId; + + /** + * 群名称 + */ + private String groupId; +} diff --git a/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java new file mode 100644 index 0000000..fcc2a46 --- /dev/null +++ b/demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java @@ -0,0 +1,29 @@ +package com.xkcoding.websocket.socketio.payload; + +import lombok.Data; + +/** + *

    + * 私聊消息载荷 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-18 17:02 + */ +@Data +public class SingleMessageRequest { + /** + * 消息发送方用户id + */ + private String fromUid; + + /** + * 消息接收方用户id + */ + private String toUid; + + /** + * 消息内容 + */ + private String message; +} diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/application.yml b/demo-websocket-socketio/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/application.yml rename to demo-websocket-socketio/src/main/resources/application.yml diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/static/bootstrap.css b/demo-websocket-socketio/src/main/resources/static/bootstrap.css similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/static/bootstrap.css rename to demo-websocket-socketio/src/main/resources/static/bootstrap.css diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/static/index.html b/demo-websocket-socketio/src/main/resources/static/index.html similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/static/index.html rename to demo-websocket-socketio/src/main/resources/static/index.html diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/static/js/jquery-1.10.1.min.js b/demo-websocket-socketio/src/main/resources/static/js/jquery-1.10.1.min.js similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/static/js/jquery-1.10.1.min.js rename to demo-websocket-socketio/src/main/resources/static/js/jquery-1.10.1.min.js diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/static/js/moment.min.js b/demo-websocket-socketio/src/main/resources/static/js/moment.min.js similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/static/js/moment.min.js rename to demo-websocket-socketio/src/main/resources/static/js/moment.min.js diff --git a/spring-boot-demo-websocket-socketio/src/main/resources/static/js/socket.io/socket.io.js b/demo-websocket-socketio/src/main/resources/static/js/socket.io/socket.io.js similarity index 100% rename from spring-boot-demo-websocket-socketio/src/main/resources/static/js/socket.io/socket.io.js rename to demo-websocket-socketio/src/main/resources/static/js/socket.io/socket.io.js diff --git a/spring-boot-demo-websocket-socketio/src/test/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplicationTests.java b/demo-websocket-socketio/src/test/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplicationTests.java similarity index 100% rename from spring-boot-demo-websocket-socketio/src/test/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplicationTests.java rename to demo-websocket-socketio/src/test/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplicationTests.java diff --git a/spring-boot-demo-zookeeper/.gitignore b/demo-websocket/.gitignore similarity index 100% rename from spring-boot-demo-zookeeper/.gitignore rename to demo-websocket/.gitignore diff --git a/demo-websocket/README.md b/demo-websocket/README.md new file mode 100644 index 0000000..d69aaff --- /dev/null +++ b/demo-websocket/README.md @@ -0,0 +1,379 @@ +# spring-boot-demo-websocket + +> 此 demo 主要演示了 Spring Boot 如何集成 WebSocket,实现后端主动往前端推送数据。网上大部分websocket的例子都是聊天室,本例主要是推送服务器状态信息。前端页面基于vue和element-ui实现。 + +## 1. 代码 + +### 1.1. pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-websocket + 1.0.0-SNAPSHOT + + spring-boot-demo-websocket + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.9.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-websocket + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +### 1.2. WebSocketConfig.java + +```java +/** + *

    + * WebSocket配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 15:58 + */ +@Configuration +@EnableWebSocket +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // 注册一个 /notification 端点,前端通过这个端点进行连接 + registry.addEndpoint("/notification") + //解决跨域问题 + .setAllowedOrigins("*") + .withSockJS(); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 + registry.enableSimpleBroker("/topic"); + } + +} +``` + +### 1.3. 服务器相关实体 + +> 此部分实体 参见包路径 [com.xkcoding.websocket.model](./src/main/java/com/xkcoding/websocket/model) + +### 1.4. ServerTask.java + +```java +/** + *

    + * 服务器定时推送任务 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:04 + */ +@Slf4j +@Component +public class ServerTask { + @Autowired + private SimpMessagingTemplate wsTemplate; + + /** + * 按照标准时间来算,每隔 2s 执行一次 + */ + @Scheduled(cron = "0/2 * * * * ?") + public void websocket() throws Exception { + log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date())); + // 查询服务器状态 + Server server = new Server(); + server.copyTo(); + ServerVO serverVO = ServerUtil.wrapServerVO(server); + Dict dict = ServerUtil.wrapServerDict(serverVO); + wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict)); + log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date())); + } +} +``` + +### 1.5. server.html + +```html + + + + + 服务器信息 + + + + +
    + + + 手动连接 + 断开连接 + + + + + +
    + CPU信息 +
    + + + + + + +
    +
    + + +
    + 内存信息 +
    + + + + + + +
    +
    +
    + + + +
    + 服务器信息 +
    + + + + + + +
    +
    +
    + + + +
    + Java虚拟机信息 +
    + + + + + + +
    +
    +
    + + + +
    + 磁盘状态 +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    + + + + + + + + +``` + +## 2. 运行方式 + +1. 启动 `SpringBootDemoWebsocketApplication.java` +2. 访问 http://localhost:8080/demo/server.html + +## 3. 运行效果 + +![image-20181217110240322](http://static.xkcoding.com/spring-boot-demo/websocket/064107.jpg) + +![image-20181217110304065](http://static.xkcoding.com/spring-boot-demo/websocket/064108.jpg) + +![image-20181217110328810](http://static.xkcoding.com/spring-boot-demo/websocket/064109.jpg) + +![image-20181217110336017](http://static.xkcoding.com/spring-boot-demo/websocket/064109-1.jpg) + +## 4. 参考 + +### 4.1. 后端 + +1. Spring Boot 整合 Websocket 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/web.html#websocket +2. 服务器信息采集 oshi 使用:https://github.com/oshi/oshi + +### 4.2. 前端 + +1. vue.js 语法:https://cn.vuejs.org/v2/guide/ +2. element-ui 用法:http://element-cn.eleme.io/#/zh-CN +3. stomp.js 用法:https://github.com/jmesnil/stomp-websocket +4. sockjs 用法:https://github.com/sockjs/sockjs-client +5. axios.js 用法:https://github.com/axios/axios#example diff --git a/demo-websocket/pom.xml b/demo-websocket/pom.xml new file mode 100644 index 0000000..6811ec1 --- /dev/null +++ b/demo-websocket/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + demo-websocket + 1.0.0-SNAPSHOT + + demo-websocket + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + 3.9.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + cn.hutool + hutool-all + + + + com.google.guava + guava + + + + org.projectlombok + lombok + true + + + + + demo-websocket + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java b/demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java new file mode 100644 index 0000000..3633fa9 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java @@ -0,0 +1,24 @@ +package com.xkcoding.websocket; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 14:58 + */ +@SpringBootApplication +@EnableScheduling +public class SpringBootDemoWebsocketApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoWebsocketApplication.class, args); + } + +} + diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java b/demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java new file mode 100644 index 0000000..1d0bec6 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java @@ -0,0 +1,13 @@ +package com.xkcoding.websocket.common; + +/** + *

    + * WebSocket常量 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:01 + */ +public interface WebSocketConsts { + String PUSH_SERVER = "/topic/server"; +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java b/demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java new file mode 100644 index 0000000..41c44ca --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java @@ -0,0 +1,37 @@ +package com.xkcoding.websocket.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +/** + *

    + * WebSocket配置 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 15:58 + */ +@Configuration +@EnableWebSocket +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // 注册一个 /notification 端点,前端通过这个端点进行连接 + registry.addEndpoint("/notification") + //解决跨域问题 + .setAllowedOrigins("*").withSockJS(); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 + registry.enableSimpleBroker("/topic"); + } + +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java b/demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java new file mode 100644 index 0000000..3dce171 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java @@ -0,0 +1,31 @@ +package com.xkcoding.websocket.controller; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.websocket.model.Server; +import com.xkcoding.websocket.payload.ServerVO; +import com.xkcoding.websocket.util.ServerUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

    + * 服务器监控Controller + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-17 10:22 + */ +@RestController +@RequestMapping("/server") +public class ServerController { + + @GetMapping + public Dict serverInfo() throws Exception { + Server server = new Server(); + server.copyTo(); + ServerVO serverVO = ServerUtil.wrapServerVO(server); + return ServerUtil.wrapServerDict(serverVO); + } + +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java new file mode 100644 index 0000000..17fb9a3 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java @@ -0,0 +1,215 @@ +package com.xkcoding.websocket.model; + +import cn.hutool.core.util.NumberUtil; +import com.xkcoding.websocket.model.server.*; +import com.xkcoding.websocket.util.IpUtil; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +/** + *

    + * 服务器相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:09 + */ +public class Server { + + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() { + return cpu; + } + + public void setCpu(Cpu cpu) { + this.cpu = cpu; + } + + public Mem getMem() { + return mem; + } + + public void setMem(Mem mem) { + this.mem = mem; + } + + public Jvm getJvm() { + return jvm; + } + + public void setJvm(Jvm jvm) { + this.jvm = jvm; + } + + public Sys getSys() { + return sys; + } + + public void setSys(Sys sys) { + this.sys = sys; + } + + public List getSysFiles() { + return sysFiles; + } + + public void setSysFiles(List sysFiles) { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() { + Properties props = System.getProperties(); + sys.setComputerName(IpUtil.getHostName()); + sys.setComputerIp(IpUtil.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) { + FileSystem fileSystem = os.getFileSystem(); + OSFileStore[] fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(NumberUtil.mul(NumberUtil.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) { + return String.format("%.1f GB", (float) size / gb); + } else if (size >= mb) { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } else if (size >= kb) { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } else { + return String.format("%d B", size); + } + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java new file mode 100644 index 0000000..af953da --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java @@ -0,0 +1,91 @@ +package com.xkcoding.websocket.model.server; + +import cn.hutool.core.util.NumberUtil; + +/** + *

    + * CPU相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:09 + */ +public class Cpu { + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() { + return cpuNum; + } + + public void setCpuNum(int cpuNum) { + this.cpuNum = cpuNum; + } + + public double getTotal() { + return NumberUtil.round(NumberUtil.mul(total, 100), 2).doubleValue(); + } + + public void setTotal(double total) { + this.total = total; + } + + public double getSys() { + return NumberUtil.round(NumberUtil.mul(sys / total, 100), 2).doubleValue(); + } + + public void setSys(double sys) { + this.sys = sys; + } + + public double getUsed() { + return NumberUtil.round(NumberUtil.mul(used / total, 100), 2).doubleValue(); + } + + public void setUsed(double used) { + this.used = used; + } + + public double getWait() { + return NumberUtil.round(NumberUtil.mul(wait / total, 100), 2).doubleValue(); + } + + public void setWait(double wait) { + this.wait = wait; + } + + public double getFree() { + return NumberUtil.round(NumberUtil.mul(free / total, 100), 2).doubleValue(); + } + + public void setFree(double free) { + this.free = free; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java new file mode 100644 index 0000000..42dcae7 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java @@ -0,0 +1,125 @@ +package com.xkcoding.websocket.model.server; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.NumberUtil; + +import java.lang.management.ManagementFactory; +import java.util.Date; + +/** + *

    + * JVM相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:09 + */ +public class Jvm { + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + /** + * JDK启动时间 + */ + private String startTime; + + /** + * JDK运行时间 + */ + private String runTime; + + public double getTotal() { + return NumberUtil.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) { + this.total = total; + } + + public double getMax() { + return NumberUtil.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) { + this.max = max; + } + + public double getFree() { + return NumberUtil.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) { + this.free = free; + } + + public double getUsed() { + return NumberUtil.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() { + return NumberUtil.mul(NumberUtil.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHome() { + return home; + } + + public void setHome(String home) { + this.home = home; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getStartTime() { + return DateUtil.formatDateTime(new Date(ManagementFactory.getRuntimeMXBean().getStartTime())); + } + + + public void setRunTime(String runTime) { + this.runTime = runTime; + } + + public String getRunTime() { + long startTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + return DateUtil.formatBetween(DateUtil.current(false) - startTime); + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java new file mode 100644 index 0000000..6d09d79 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java @@ -0,0 +1,56 @@ +package com.xkcoding.websocket.model.server; + +import cn.hutool.core.util.NumberUtil; + +/** + *

    + * 內存相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:09 + */ +public class Mem { + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() { + return NumberUtil.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) { + this.total = total; + } + + public double getUsed() { + return NumberUtil.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) { + this.used = used; + } + + public double getFree() { + return NumberUtil.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) { + this.free = free; + } + + public double getUsage() { + return NumberUtil.mul(NumberUtil.div(used, total, 4), 100); + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java new file mode 100644 index 0000000..ee03793 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java @@ -0,0 +1,76 @@ +package com.xkcoding.websocket.model.server; + +/** + *

    + * 系统相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:10 + */ +public class Sys { + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() { + return computerName; + } + + public void setComputerName(String computerName) { + this.computerName = computerName; + } + + public String getComputerIp() { + return computerIp; + } + + public void setComputerIp(String computerIp) { + this.computerIp = computerIp; + } + + public String getUserDir() { + return userDir; + } + + public void setUserDir(String userDir) { + this.userDir = userDir; + } + + public String getOsName() { + return osName; + } + + public void setOsName(String osName) { + this.osName = osName; + } + + public String getOsArch() { + return osArch; + } + + public void setOsArch(String osArch) { + this.osArch = osArch; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java new file mode 100644 index 0000000..00c63f7 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java @@ -0,0 +1,102 @@ +package com.xkcoding.websocket.model.server; + +/** + *

    + * 系统文件相关信息实体 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:10 + */ +public class SysFile { + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() { + return dirName; + } + + public void setDirName(String dirName) { + this.dirName = dirName; + } + + public String getSysTypeName() { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public String getTotal() { + return total; + } + + public void setTotal(String total) { + this.total = total; + } + + public String getFree() { + return free; + } + + public void setFree(String free) { + this.free = free; + } + + public String getUsed() { + return used; + } + + public void setUsed(String used) { + this.used = used; + } + + public double getUsage() { + return usage; + } + + public void setUsage(double usage) { + this.usage = usage; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java new file mode 100644 index 0000000..469d24a --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java @@ -0,0 +1,28 @@ +package com.xkcoding.websocket.payload; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

    + * 键值匹配 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:41 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KV { + /** + * 键 + */ + private String key; + + /** + * 值 + */ + private Object value; +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java new file mode 100644 index 0000000..4d97cd0 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java @@ -0,0 +1,34 @@ +package com.xkcoding.websocket.payload; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.Server; +import com.xkcoding.websocket.payload.server.*; +import lombok.Data; + +import java.util.List; + +/** + *

    + * 服务器信息VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:25 + */ +@Data +public class ServerVO { + List cpu = Lists.newArrayList(); + List jvm = Lists.newArrayList(); + List mem = Lists.newArrayList(); + List sysFile = Lists.newArrayList(); + List sys = Lists.newArrayList(); + + public ServerVO create(Server server) { + cpu.add(CpuVO.create(server.getCpu())); + jvm.add(JvmVO.create(server.getJvm())); + mem.add(MemVO.create(server.getMem())); + sysFile.add(SysFileVO.create(server.getSysFiles())); + sys.add(SysVO.create(server.getSys())); + return null; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java new file mode 100644 index 0000000..73b7bd1 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java @@ -0,0 +1,32 @@ +package com.xkcoding.websocket.payload.server; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.server.Cpu; +import com.xkcoding.websocket.payload.KV; +import lombok.Data; + +import java.util.List; + +/** + *

    + * CPU相关信息实体VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:27 + */ +@Data +public class CpuVO { + List data = Lists.newArrayList(); + + public static CpuVO create(Cpu cpu) { + CpuVO vo = new CpuVO(); + vo.data.add(new KV("核心数", cpu.getCpuNum())); + vo.data.add(new KV("CPU总的使用率", cpu.getTotal())); + vo.data.add(new KV("CPU系统使用率", cpu.getSys() + "%")); + vo.data.add(new KV("CPU用户使用率", cpu.getUsed() + "%")); + vo.data.add(new KV("CPU当前等待率", cpu.getWait() + "%")); + vo.data.add(new KV("CPU当前空闲率", cpu.getFree() + "%")); + return vo; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java new file mode 100644 index 0000000..77285ae --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java @@ -0,0 +1,35 @@ +package com.xkcoding.websocket.payload.server; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.server.Jvm; +import com.xkcoding.websocket.payload.KV; +import lombok.Data; + +import java.util.List; + +/** + *

    + * JVM相关信息实体VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:28 + */ +@Data +public class JvmVO { + List data = Lists.newArrayList(); + + public static JvmVO create(Jvm jvm) { + JvmVO vo = new JvmVO(); + vo.data.add(new KV("当前JVM占用的内存总数(M)", jvm.getTotal() + "M")); + vo.data.add(new KV("JVM最大可用内存总数(M)", jvm.getMax() + "M")); + vo.data.add(new KV("JVM空闲内存(M)", jvm.getFree() + "M")); + vo.data.add(new KV("JVM使用率", jvm.getUsage() + "%")); + vo.data.add(new KV("JDK版本", jvm.getVersion())); + vo.data.add(new KV("JDK路径", jvm.getHome())); + vo.data.add(new KV("JDK启动时间", jvm.getStartTime())); + vo.data.add(new KV("JDK运行时间", jvm.getRunTime())); + return vo; + } + +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java new file mode 100644 index 0000000..7ab709f --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java @@ -0,0 +1,30 @@ +package com.xkcoding.websocket.payload.server; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.server.Mem; +import com.xkcoding.websocket.payload.KV; +import lombok.Data; + +import java.util.List; + +/** + *

    + * 內存相关信息实体VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:28 + */ +@Data +public class MemVO { + List data = Lists.newArrayList(); + + public static MemVO create(Mem mem) { + MemVO vo = new MemVO(); + vo.data.add(new KV("内存总量", mem.getTotal() + "G")); + vo.data.add(new KV("已用内存", mem.getUsed() + "G")); + vo.data.add(new KV("剩余内存", mem.getFree() + "G")); + vo.data.add(new KV("使用率", mem.getUsage() + "%")); + return vo; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java new file mode 100644 index 0000000..c4b767e --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java @@ -0,0 +1,39 @@ +package com.xkcoding.websocket.payload.server; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.server.SysFile; +import com.xkcoding.websocket.payload.KV; +import lombok.Data; + +import java.util.List; + +/** + *

    + * 系统文件相关信息实体VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:30 + */ +@Data +public class SysFileVO { + List> data = Lists.newArrayList(); + + public static SysFileVO create(List sysFiles) { + SysFileVO vo = new SysFileVO(); + for (SysFile sysFile : sysFiles) { + List item = Lists.newArrayList(); + + item.add(new KV("盘符路径", sysFile.getDirName())); + item.add(new KV("盘符类型", sysFile.getSysTypeName())); + item.add(new KV("文件类型", sysFile.getTypeName())); + item.add(new KV("总大小", sysFile.getTotal())); + item.add(new KV("剩余大小", sysFile.getFree())); + item.add(new KV("已经使用量", sysFile.getUsed())); + item.add(new KV("资源的使用率", sysFile.getUsage() + "%")); + + vo.data.add(item); + } + return vo; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java new file mode 100644 index 0000000..a3a0029 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java @@ -0,0 +1,31 @@ +package com.xkcoding.websocket.payload.server; + +import com.google.common.collect.Lists; +import com.xkcoding.websocket.model.server.Sys; +import com.xkcoding.websocket.payload.KV; +import lombok.Data; + +import java.util.List; + +/** + *

    + * 系统相关信息实体VO + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 17:28 + */ +@Data +public class SysVO { + List data = Lists.newArrayList(); + + public static SysVO create(Sys sys) { + SysVO vo = new SysVO(); + vo.data.add(new KV("服务器名称", sys.getComputerName())); + vo.data.add(new KV("服务器Ip", sys.getComputerIp())); + vo.data.add(new KV("项目路径", sys.getUserDir())); + vo.data.add(new KV("操作系统", sys.getOsName())); + vo.data.add(new KV("系统架构", sys.getOsArch())); + return vo; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java b/demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java new file mode 100644 index 0000000..8088161 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java @@ -0,0 +1,46 @@ +package com.xkcoding.websocket.task; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.json.JSONUtil; +import com.xkcoding.websocket.common.WebSocketConsts; +import com.xkcoding.websocket.model.Server; +import com.xkcoding.websocket.payload.ServerVO; +import com.xkcoding.websocket.util.ServerUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + *

    + * 服务器定时推送任务 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:04 + */ +@Slf4j +@Component +public class ServerTask { + @Autowired + private SimpMessagingTemplate wsTemplate; + + /** + * 按照标准时间来算,每隔 2s 执行一次 + */ + @Scheduled(cron = "0/2 * * * * ?") + public void websocket() throws Exception { + log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date())); + // 查询服务器状态 + Server server = new Server(); + server.copyTo(); + ServerVO serverVO = ServerUtil.wrapServerVO(server); + Dict dict = ServerUtil.wrapServerDict(serverVO); + wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict)); + log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date())); + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java b/demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java new file mode 100644 index 0000000..647d570 --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java @@ -0,0 +1,164 @@ +package com.xkcoding.websocket.util; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + *

    + * IP 工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-14 16:08 + */ +public class IpUtil { + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; + } + + public static boolean internalIp(String ip) { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + private static boolean internalIp(byte[] addr) { + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) { + return true; + } + case SECTION_5: + switch (b1) { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) { + if (text.length() == 0) { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try { + long l; + int i; + switch (elements.length) { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } catch (NumberFormatException e) { + return null; + } + return bytes; + } + + public static String getHostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + } + return "127.0.0.1"; + } + + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + } + return "未知"; + } +} diff --git a/demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java b/demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java new file mode 100644 index 0000000..31952cd --- /dev/null +++ b/demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java @@ -0,0 +1,38 @@ +package com.xkcoding.websocket.util; + +import cn.hutool.core.lang.Dict; +import com.xkcoding.websocket.model.Server; +import com.xkcoding.websocket.payload.ServerVO; + +/** + *

    + * 服务器转换工具类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-17 10:24 + */ +public class ServerUtil { + /** + * 包装成 ServerVO + * + * @param server server + * @return ServerVO + */ + public static ServerVO wrapServerVO(Server server) { + ServerVO serverVO = new ServerVO(); + serverVO.create(server); + return serverVO; + } + + /** + * 包装成 Dict + * + * @param serverVO serverVO + * @return Dict + */ + public static Dict wrapServerDict(ServerVO serverVO) { + Dict dict = Dict.create().set("cpu", serverVO.getCpu().get(0).getData()).set("mem", serverVO.getMem().get(0).getData()).set("sys", serverVO.getSys().get(0).getData()).set("jvm", serverVO.getJvm().get(0).getData()).set("sysFile", serverVO.getSysFile().get(0).getData()); + return dict; + } +} diff --git a/demo-websocket/src/main/resources/application.yml b/demo-websocket/src/main/resources/application.yml new file mode 100644 index 0000000..a02fbde --- /dev/null +++ b/demo-websocket/src/main/resources/application.yml @@ -0,0 +1,4 @@ +server: + port: 8080 + servlet: + context-path: /demo \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/resources/static/js/sockjs.min.js b/demo-websocket/src/main/resources/static/js/sockjs.min.js similarity index 100% rename from spring-boot-demo-websocket/src/main/resources/static/js/sockjs.min.js rename to demo-websocket/src/main/resources/static/js/sockjs.min.js diff --git a/spring-boot-demo-websocket/src/main/resources/static/js/stomp.js b/demo-websocket/src/main/resources/static/js/stomp.js similarity index 100% rename from spring-boot-demo-websocket/src/main/resources/static/js/stomp.js rename to demo-websocket/src/main/resources/static/js/stomp.js diff --git a/spring-boot-demo-websocket/src/main/resources/static/server.html b/demo-websocket/src/main/resources/static/server.html similarity index 100% rename from spring-boot-demo-websocket/src/main/resources/static/server.html rename to demo-websocket/src/main/resources/static/server.html diff --git a/spring-boot-demo-websocket/src/test/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplicationTests.java b/demo-websocket/src/test/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplicationTests.java similarity index 100% rename from spring-boot-demo-websocket/src/test/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplicationTests.java rename to demo-websocket/src/test/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplicationTests.java diff --git a/demo-zookeeper/.gitignore b/demo-zookeeper/.gitignore new file mode 100644 index 0000000..82eca33 --- /dev/null +++ b/demo-zookeeper/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/demo-zookeeper/README.md b/demo-zookeeper/README.md new file mode 100644 index 0000000..15d10b9 --- /dev/null +++ b/demo-zookeeper/README.md @@ -0,0 +1,436 @@ +# spring-boot-demo-zookeeper + +> 此 demo 主要演示了如何使用 Spring Boot 集成 Zookeeper 结合AOP实现分布式锁。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-zookeeper + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-zookeeper + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-aop + + + + + + org.apache.curator + curator-recipes + 4.1.0 + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-zookeeper + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## ZkProps.java + +```java +/** + *

    + * Zookeeper 配置项 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:47 + */ +@Data +@ConfigurationProperties(prefix = "zk") +public class ZkProps { + /** + * 连接地址 + */ + private String url; + + /** + * 超时时间(毫秒),默认1000 + */ + private int timeout = 1000; + + /** + * 重试次数,默认3 + */ + private int retry = 3; +} +``` + +## application.yml + +```yaml +server: + port: 8080 + servlet: + context-path: /demo + +zk: + url: 127.0.0.1:2181 + timeout: 1000 + retry: 3 +``` + +## ZkConfig.java + +```java +/** + *

    + * Zookeeper配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:45 + */ +@Configuration +@EnableConfigurationProperties(ZkProps.class) +public class ZkConfig { + private final ZkProps zkProps; + + @Autowired + public ZkConfig(ZkProps zkProps) { + this.zkProps = zkProps; + } + + @Bean + public CuratorFramework curatorFramework() { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(zkProps.getTimeout(), zkProps.getRetry()); + CuratorFramework client = CuratorFrameworkFactory.newClient(zkProps.getUrl(), retryPolicy); + client.start(); + return client; + } +} +``` + +## ZooLock.java + +> 分布式锁的关键注解 + +```java +/** + *

    + * 基于Zookeeper的分布式锁注解 + * 在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:11 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ZooLock { + /** + * 分布式锁的键 + */ + String key(); + + /** + * 锁释放时间,默认五秒 + */ + long timeout() default 5 * 1000; + + /** + * 时间格式,默认:毫秒 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} +``` + +## LockKeyParam.java + +> 分布式锁动态key的关键注解 + +```java +/** + *

    + * 分布式锁动态key注解,配置之后key的值会动态获取参数内容 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:17 + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface LockKeyParam { + /** + * 如果动态key在user对象中,那么就需要设置fields的值为user对象中的属性名可以为多个,基本类型则不需要设置该值 + *

    例1:public void count(@LockKeyParam({"id"}) User user) + *

    例2:public void count(@LockKeyParam({"id","userName"}) User user) + *

    例3:public void count(@LockKeyParam String userId) + */ + String[] fields() default {}; +} +``` + +## ZooLockAspect.java + +> 分布式锁的关键部分 + +```java +/** + *

    + * 使用 aop 切面记录请求日志信息 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-01 22:05 + */ +@Aspect +@Component +@Slf4j +public class ZooLockAspect { + private final CuratorFramework zkClient; + + private static final String KEY_PREFIX = "DISTRIBUTED_LOCK_"; + + private static final String KEY_SEPARATOR = "/"; + + @Autowired + public ZooLockAspect(CuratorFramework zkClient) { + this.zkClient = zkClient; + } + + /** + * 切入点 + */ + @Pointcut("@annotation(com.xkcoding.zookeeper.annotation.ZooLock)") + public void doLock() { + + } + + /** + * 环绕操作 + * + * @param point 切入点 + * @return 原方法返回值 + * @throws Throwable 异常信息 + */ + @Around("doLock()") + public Object around(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Object[] args = point.getArgs(); + ZooLock zooLock = method.getAnnotation(ZooLock.class); + if (StrUtil.isBlank(zooLock.key())) { + throw new RuntimeException("分布式锁键不能为空"); + } + String lockKey = buildLockKey(zooLock, method, args); + InterProcessMutex lock = new InterProcessMutex(zkClient, lockKey); + try { + // 假设上锁成功,以后拿到的都是 false + if (lock.acquire(zooLock.timeout(), zooLock.timeUnit())) { + return point.proceed(); + } else { + throw new RuntimeException("请勿重复提交"); + } + } finally { + lock.release(); + } + } + + /** + * 构造分布式锁的键 + * + * @param lock 注解 + * @param method 注解标记的方法 + * @param args 方法上的参数 + * @return + * @throws NoSuchFieldException + * @throws IllegalAccessException + */ + private String buildLockKey(ZooLock lock, Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { + StringBuilder key = new StringBuilder(KEY_SEPARATOR + KEY_PREFIX + lock.key()); + + // 迭代全部参数的注解,根据使用LockKeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上 + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + + for (int i = 0; i < parameterAnnotations.length; i++) { + // 循环该参数全部注解 + for (Annotation annotation : parameterAnnotations[i]) { + // 注解不是 @LockKeyParam + if (!annotation.annotationType().isInstance(LockKeyParam.class)) { + continue; + } + + // 获取所有fields + String[] fields = ((LockKeyParam) annotation).fields(); + if (ArrayUtil.isEmpty(fields)) { + // 普通数据类型直接拼接 + if (ObjectUtil.isNull(args[i])) { + throw new RuntimeException("动态参数不能为null"); + } + key.append(KEY_SEPARATOR).append(args[i]); + } else { + // @LockKeyParam的fields值不为null,所以当前参数应该是对象类型 + for (String field : fields) { + Class clazz = args[i].getClass(); + Field declaredField = clazz.getDeclaredField(field); + declaredField.setAccessible(true); + Object value = declaredField.get(clazz); + key.append(KEY_SEPARATOR).append(value); + } + } + } + } + return key.toString(); + } + +} +``` + +## SpringBootDemoZookeeperApplicationTests.java + +> 测试分布式锁 + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class SpringBootDemoZookeeperApplicationTests { + + public Integer getCount() { + return count; + } + + private Integer count = 10000; + private ExecutorService executorService = Executors.newFixedThreadPool(1000); + + @Autowired + private CuratorFramework zkClient; + + /** + * 不使用分布式锁,程序结束查看count的值是否为0 + */ + @Test + public void test() throws InterruptedException { + IntStream.range(0, 10000).forEach(i -> executorService.execute(this::doBuy)); + TimeUnit.MINUTES.sleep(1); + log.error("count值为{}", count); + } + + /** + * 测试AOP分布式锁 + */ + @Test + public void testAopLock() throws InterruptedException { + // 测试类中使用AOP需要手动代理 + SpringBootDemoZookeeperApplicationTests target = new SpringBootDemoZookeeperApplicationTests(); + AspectJProxyFactory factory = new AspectJProxyFactory(target); + ZooLockAspect aspect = new ZooLockAspect(zkClient); + factory.addAspect(aspect); + SpringBootDemoZookeeperApplicationTests proxy = factory.getProxy(); + IntStream.range(0, 10000).forEach(i -> executorService.execute(() -> proxy.aopBuy(i))); + TimeUnit.MINUTES.sleep(1); + log.error("count值为{}", proxy.getCount()); + } + + /** + * 测试手动加锁 + */ + @Test + public void testManualLock() throws InterruptedException { + IntStream.range(0, 10000).forEach(i -> executorService.execute(this::manualBuy)); + TimeUnit.MINUTES.sleep(1); + log.error("count值为{}", count); + } + + @ZooLock(key = "buy", timeout = 1, timeUnit = TimeUnit.MINUTES) + public void aopBuy(int userId) { + log.info("{} 正在出库。。。", userId); + doBuy(); + log.info("{} 扣库存成功。。。", userId); + } + + public void manualBuy() { + String lockPath = "/buy"; + log.info("try to buy sth."); + try { + InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath); + try { + if (lock.acquire(1, TimeUnit.MINUTES)) { + doBuy(); + log.info("buy successfully!"); + } + } finally { + lock.release(); + } + } catch (Exception e) { + log.error("zk error"); + } + } + + public void doBuy() { + count--; + log.info("count值为{}", count); + } + +} +``` + +## 参考 + +1. [如何在测试类中使用 AOP](https://stackoverflow.com/questions/11436600/unit-testing-spring-around-aop-methods) +2. zookeeper 实现分布式锁:《Spring Boot 2精髓 从构建小系统到架构分布式大系统》李家智 - 第16章 - Spring Boot 和 Zoo Keeper - 16.3 实现分布式锁 diff --git a/demo-zookeeper/pom.xml b/demo-zookeeper/pom.xml new file mode 100644 index 0000000..a22a8cb --- /dev/null +++ b/demo-zookeeper/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + demo-zookeeper + 1.0.0-SNAPSHOT + jar + + demo-zookeeper + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-aop + + + + + + org.apache.curator + curator-recipes + 4.1.0 + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + demo-zookeeper + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java new file mode 100644 index 0000000..766fbcf --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java @@ -0,0 +1,22 @@ +package com.xkcoding.zookeeper; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

    + * 启动器 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:51 + */ +@SpringBootApplication +public class SpringBootDemoZookeeperApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoZookeeperApplication.class, args); + } + +} + diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java new file mode 100644 index 0000000..1f2d302 --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java @@ -0,0 +1,25 @@ +package com.xkcoding.zookeeper.annotation; + +import java.lang.annotation.*; + +/** + *

    + * 分布式锁动态key注解,配置之后key的值会动态获取参数内容 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:17 + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface LockKeyParam { + /** + * 如果动态key在user对象中,那么就需要设置fields的值为user对象中的属性名可以为多个,基本类型则不需要设置该值 + *

    例1:public void count(@LockKeyParam({"id"}) User user) + *

    例2:public void count(@LockKeyParam({"id","userName"}) User user) + *

    例3:public void count(@LockKeyParam String userId) + */ + String[] fields() default {}; +} diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java new file mode 100644 index 0000000..6e0f562 --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java @@ -0,0 +1,34 @@ +package com.xkcoding.zookeeper.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + *

    + * 基于Zookeeper的分布式锁注解 + * 在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:11 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ZooLock { + /** + * 分布式锁的键 + */ + String key(); + + /** + * 锁释放时间,默认五秒 + */ + long timeout() default 5 * 1000; + + /** + * 时间格式,默认:毫秒 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java new file mode 100644 index 0000000..068b7f0 --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java @@ -0,0 +1,131 @@ +package com.xkcoding.zookeeper.aspectj; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.xkcoding.zookeeper.annotation.LockKeyParam; +import com.xkcoding.zookeeper.annotation.ZooLock; +import lombok.extern.slf4j.Slf4j; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + *

    + * 使用 aop 切面记录请求日志信息 + *

    + * + * @author yangkai.shen + * @date Created in 2018-10-01 22:05 + */ +@Aspect +@Component +@Slf4j +public class ZooLockAspect { + private final CuratorFramework zkClient; + + private static final String KEY_PREFIX = "DISTRIBUTED_LOCK_"; + + private static final String KEY_SEPARATOR = "/"; + + @Autowired + public ZooLockAspect(CuratorFramework zkClient) { + this.zkClient = zkClient; + } + + /** + * 切入点 + */ + @Pointcut("@annotation(com.xkcoding.zookeeper.annotation.ZooLock)") + public void doLock() { + + } + + /** + * 环绕操作 + * + * @param point 切入点 + * @return 原方法返回值 + * @throws Throwable 异常信息 + */ + @Around("doLock()") + public Object around(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Object[] args = point.getArgs(); + ZooLock zooLock = method.getAnnotation(ZooLock.class); + if (StrUtil.isBlank(zooLock.key())) { + throw new RuntimeException("分布式锁键不能为空"); + } + String lockKey = buildLockKey(zooLock, method, args); + InterProcessMutex lock = new InterProcessMutex(zkClient, lockKey); + try { + // 假设上锁成功,以后拿到的都是 false + if (lock.acquire(zooLock.timeout(), zooLock.timeUnit())) { + return point.proceed(); + } else { + throw new RuntimeException("请勿重复提交"); + } + } finally { + lock.release(); + } + } + + /** + * 构造分布式锁的键 + * + * @param lock 注解 + * @param method 注解标记的方法 + * @param args 方法上的参数 + * @return + * @throws NoSuchFieldException + * @throws IllegalAccessException + */ + private String buildLockKey(ZooLock lock, Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { + StringBuilder key = new StringBuilder(KEY_SEPARATOR + KEY_PREFIX + lock.key()); + + // 迭代全部参数的注解,根据使用LockKeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上 + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + + for (int i = 0; i < parameterAnnotations.length; i++) { + // 循环该参数全部注解 + for (Annotation annotation : parameterAnnotations[i]) { + // 注解不是 @LockKeyParam + if (!annotation.annotationType().isInstance(LockKeyParam.class)) { + continue; + } + + // 获取所有fields + String[] fields = ((LockKeyParam) annotation).fields(); + if (ArrayUtil.isEmpty(fields)) { + // 普通数据类型直接拼接 + if (ObjectUtil.isNull(args[i])) { + throw new RuntimeException("动态参数不能为null"); + } + key.append(KEY_SEPARATOR).append(args[i]); + } else { + // @LockKeyParam的fields值不为null,所以当前参数应该是对象类型 + for (String field : fields) { + Class clazz = args[i].getClass(); + Field declaredField = clazz.getDeclaredField(field); + declaredField.setAccessible(true); + Object value = declaredField.get(clazz); + key.append(KEY_SEPARATOR).append(value); + } + } + } + } + return key.toString(); + } + +} diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java new file mode 100644 index 0000000..3c25c69 --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java @@ -0,0 +1,38 @@ +package com.xkcoding.zookeeper.config; + +import com.xkcoding.zookeeper.config.props.ZkProps; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

    + * Zookeeper配置类 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:45 + */ +@Configuration +@EnableConfigurationProperties(ZkProps.class) +public class ZkConfig { + private final ZkProps zkProps; + + @Autowired + public ZkConfig(ZkProps zkProps) { + this.zkProps = zkProps; + } + + @Bean + public CuratorFramework curatorFramework() { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(zkProps.getTimeout(), zkProps.getRetry()); + CuratorFramework client = CuratorFrameworkFactory.newClient(zkProps.getUrl(), retryPolicy); + client.start(); + return client; + } +} diff --git a/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java new file mode 100644 index 0000000..561c55b --- /dev/null +++ b/demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java @@ -0,0 +1,31 @@ +package com.xkcoding.zookeeper.config.props; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

    + * Zookeeper 配置项 + *

    + * + * @author yangkai.shen + * @date Created in 2018-12-27 14:47 + */ +@Data +@ConfigurationProperties(prefix = "zk") +public class ZkProps { + /** + * 连接地址 + */ + private String url; + + /** + * 超时时间(毫秒),默认1000 + */ + private int timeout = 1000; + + /** + * 重试次数,默认3 + */ + private int retry = 3; +} diff --git a/spring-boot-demo-zookeeper/src/main/resources/application.yml b/demo-zookeeper/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-zookeeper/src/main/resources/application.yml rename to demo-zookeeper/src/main/resources/application.yml diff --git a/spring-boot-demo-zookeeper/src/test/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplicationTests.java b/demo-zookeeper/src/test/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplicationTests.java similarity index 100% rename from spring-boot-demo-zookeeper/src/test/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplicationTests.java rename to demo-zookeeper/src/test/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplicationTests.java diff --git a/jd.md b/jd.md new file mode 100644 index 0000000..ed16529 --- /dev/null +++ b/jd.md @@ -0,0 +1,145 @@ +> 公司:杭州涂鸦信息科技有限公司,是一个全球云开发平台、AI+IoT开发者平台,连接消费者、制造品牌、OEM厂商和连锁零售商的智能化需求,为开发者提供一站式人工智能物联网的PaaS级解决方案。并且涵盖了硬件开发工具、全球云、智慧商业平台开发三方面,提供从技术到营销渠道的全面生态赋能,打造世界领先的IoT OS。 +> +> 团队:云端开发部/数据平台 +> +> Base:杭州 + +组内招人啦,HC 巨多 ~~ 感兴趣的小伙伴,简历发过来,:kissing_heart: + +> 微信:syk941020 +> +> 邮箱:237497819@qq.com +> +> 备注:内推+岗位 + +--- + +# 岗位列表 + +* [岗位列表](#岗位列表) + * [高级java开发-大数据方向](#高级java开发-大数据方向) + * [【职位描述】](#职位描述) + * [【职位要求】](#职位要求) + * [高级java大数据平台开发工程师](#高级java大数据平台开发工程师) + * [【职位描述】](#职位描述-1) + * [【职位要求】](#职位要求-1) + * [大数据开发工程师(flink方向)](#大数据开发工程师flink方向) + * [【岗位职责】](#岗位职责) + * [【任职要求】](#任职要求) + * [高级数据仓库开发工程师](#高级数据仓库开发工程师) + * [【岗位描述】](#岗位描述) + * [【技能要求】](#技能要求) + * [bi分析师](#bi分析师) + * [【岗位职责】](#岗位职责-1) + * [【任职要求】](#任职要求-1) + * [大数据平台架构师](#大数据平台架构师) + * [【职位描述】](#职位描述-2) + * [【职位要求】](#职位要求-2) + +## 高级java开发-大数据方向 + +### 【职位描述】 + +1. 精通Java开源框架,Java开发语言 +3. 对新技术有出色的学习能力,掌握 mybatis, Spring MVC等技术 +3. 参与公司大数据产品、核心架构的研发和方向预演; +4. 思维开阔喜问乐学,以提升自己的能力和效率; + +### 【职位要求】 + +1. 精通Java语言,对相关技术领域的开源产品有深入的理解; +2. 希望你有3年以上java相关经验; +3. 熟悉Linux下的常用系统工具, 能利用工具排查CPU, 内存, IO等系统问题; +4. 从事过大规模 Web 应用开发,熟悉代码重构,性能优化,系统安全和高可用性; +5. 熟悉非关系型数据库如Redis、Hbase等。 +6. 有过hbase,elasticsearch,flink,tidb,clickhouse的开发经验,对这5者有一个深入研究者优先。 +7. 有过数据应用产品相关开发经验优先。 + +## 高级java大数据平台开发工程师 + +### 【职位描述】 + +1. 负责大数据平台的设计与开发实现 +2. 负责大数据应用相关产品需求分析、架构设计以及开发实现 +3. 负责数据产品的服务接口开发和维护 + +### 【职位要求】 + +1. 本科及以上学历,2年及以上大数据相关技术背景 +2. 熟练进行Java的代码编写,良好的代码编写素养,良好的数据结构算法技能。 +3. 熟悉spring boot、mybatis、dubbo等开发框架,熟悉前后端分离开发流程 +4. 有大数据平台开发经验,包括但不限于离线开发平台、数据质量中心、元数据管理、数据资产管理,实时流平台,可视化报表等 +5. 熟悉开源大数据平台如HBase、ES、Kylin、tidb、clickhouse等相关技术 +6. 有过使用flink做实时计算平台成功案例者和用过hera系统做过离线任务平台者优先。 + +## 大数据开发工程师(flink方向) + +### 【岗位职责】 + +1. 负责业务数据和用户行为日志的实时采集、计算、存储、服务,为业务团队提供直接数据决策; +2. 负责部门实时计算体系架构建设及实时计算平台开发改进。 +3. 负责即时分析相关技术方案的探索 +4. 负责实时数据仓库的建设,完善实时计算方案 + +### 【任职要求】 + +1. 深入了解离线计算及相关开发,掌握实时计算技术体系包括数据采集、计算引擎flink等,对实时计算所涉及的事务、容错、可靠性有深入理解 并有实际项目经验; +2. 熟悉 hadoop 生态包括 hdfs/mapreduce/hive/hbase,熟悉 kafka 等实时开源工具并有项目经验; +3. 熟悉 mysql 等关系型数据库,熟悉 redis 内存数据库,熟悉 linux 系统; +4. 掌握Java或Scala语言,如并发编程和JVM等,追求高标准的工程质量; +5. 有flink实时计算开发经验,熟悉olap的相关技术。 +6. 有良好的沟通能力和自我驱动动力,具备出色的规划、执行力,强烈的责任感,以及优秀的学习能力,对技术有热情,愿意不断尝试新技术和业务挑战。 + +## 高级数据仓库开发工程师 + +### 【岗位描述】 + +1. 负责数据仓库架构设计、建模和ETL开发; +2. 参与数据治理工作,提升数据易用性及数据质量; +3. 理解并合理抽象业务需求,发挥数据价值,与业务、BI团队紧密合作。 + +### 【技能要求】 + +1. 有数据仓库需求调研和需求分析经验,能根据业务需求设计数据仓库模型,并对数据仓库数据模型进行管理,保证数据质量。 +2. 精通sql开发,有较丰富的spark sql性能调优经验优先; +3. 精通数据仓库实施方法论、深入了解数据仓库体系,并支撑过实际业务场景; +4. 熟悉数据治理的相关环节、有相关开发经验或者实际应用场景; +5. 具备较强的编码能力,熟悉sql,python,hive,spark,kafka,storm中的多项; +6. 对数据敏感,认真细致,善于从数据中发现疑点; +7. 善于沟通,具备优秀的技术与业务结合能力。 + +## bi分析师 + +### 【岗位职责】 + +1. 为公司技术,运营,产品,业务策略等提供数据支持; +2. 维护,完善数据报表体系,及时,准确监控运营状况,并提供专业分析报告; +3. 通过数据来发现业务、流程中的问题、机会,从数据角度为业务部门提出相应的优化建议,并与多方合作实现流程改善,推动相关业务目标达成; +4. 沉淀分析思路与框架,提炼数据产品需求,与相关团队协作并推动数据产品的落地; + +### 【任职要求】 + +1. 本科以上学历,2年以上工作经验,有过互联网数据分析经验者优先; +2. 扎实的数据分析、数据统计理论,善于对抽象问题进行概括; +3. 精通Excel,熟练SQL查询等操作,熟练使用至少一种数据分析工具(R、Python、SPSS等)者优先; +4. 具有良好的学习能力、沟通表达能力和团队协作能力。 + +## 大数据平台架构师 + +### 【职位描述】 + +1. 负责涂鸦大数据平台的开发建设,建立数据生态服务,解决海量数据面临的挑战 +2. 参与大数据平台各类基础系统架构设计和引擎开发,集群优化,技术难点攻关 +3. 集群数据安全相关体系建设,各种存储,查询方案构建 +4. 协助管理、优化并维护Hadoop、Spark、flink等集群,保证集群规模持续、稳定; +5. 负责大数据产品的自动化、离线与实时计算、即席计算、数据质量、数据安全等平台的设计和开发; +6. 调研和把握当前的最新技术,将其中的先进技术引入到自己的平台中,改善产品,提升竞争力 + +### 【职位要求】 + +1. 本科及以上学历,5年以上工作经验,3年以上大数据领域工作经验,熟悉java,spark +2. 熟悉开源大数据平台如HBase、ES、Kylin、Druid等,有实际的报表平台、多维度分析工具、etl平台、调度平台、实时平台中至少两种工具的实际建设经验。 +3. 有上述相关系统为基础的实际成功的复杂系统项目的架构和开发经验 +4. 热爱开源技术,熟悉一种或者多种大数据生态技术(Kafka、Hive、Hbase、Spark、Storm、Hadoop、Flink、kudu、clickhouse、tidb等),熟悉源码者优先 +5. 相关开源领域的活跃贡献者或大型互联网公司相关从业经验者优先. +6. 有过使用flink做实时计算平台成功案例者和用过hera系统做过离线任务平台者优先。 diff --git a/pom.xml b/pom.xml index 587e773..0110e64 100644 --- a/pom.xml +++ b/pom.xml @@ -8,67 +8,68 @@ spring-boot-demo 1.0.0-SNAPSHOT - spring-boot-demo-helloworld - spring-boot-demo-properties - spring-boot-demo-actuator - spring-boot-demo-admin - spring-boot-demo-logback - spring-boot-demo-log-aop - spring-boot-demo-exception-handler - spring-boot-demo-template-freemarker - spring-boot-demo-template-thymeleaf - spring-boot-demo-template-beetl - spring-boot-demo-template-enjoy - spring-boot-demo-orm-jdbctemplate - spring-boot-demo-orm-jpa - spring-boot-demo-orm-mybatis - spring-boot-demo-orm-mybatis-mapper-page - spring-boot-demo-orm-mybatis-plus - spring-boot-demo-orm-beetlsql - spring-boot-demo-upload - spring-boot-demo-cache-redis - spring-boot-demo-cache-ehcache - spring-boot-demo-email - spring-boot-demo-task - spring-boot-demo-task-quartz - spring-boot-demo-task-xxl-job - spring-boot-demo-swagger - spring-boot-demo-swagger-beauty - spring-boot-demo-rbac-security - spring-boot-demo-rbac-shiro - spring-boot-demo-session - spring-boot-demo-oauth - spring-boot-demo-social - spring-boot-demo-zookeeper - spring-boot-demo-mq-rabbitmq - spring-boot-demo-mq-rocketmq - spring-boot-demo-mq-kafka - spring-boot-demo-websocket - spring-boot-demo-websocket-socketio - spring-boot-demo-ureport2 - spring-boot-demo-uflo - spring-boot-demo-urule - spring-boot-demo-activiti - spring-boot-demo-async - spring-boot-demo-dubbo - spring-boot-demo-war - spring-boot-demo-elasticsearch - spring-boot-demo-mongodb - spring-boot-demo-neo4j - spring-boot-demo-docker - spring-boot-demo-multi-datasource-jpa - spring-boot-demo-multi-datasource-mybatis - spring-boot-demo-sharding-jdbc - spring-boot-demo-tio - spring-boot-demo-codegen - spring-boot-demo-graylog - spring-boot-demo-ldap - spring-boot-demo-dynamic-datasource - spring-boot-demo-ratelimit-guava - spring-boot-demo-ratelimit-redis - spring-boot-demo-elasticsearch-rest-high-level-client - spring-boot-demo-https - spring-boot-demo-flyway + demo-helloworld + demo-properties + demo-actuator + demo-admin + demo-logback + demo-log-aop + demo-exception-handler + demo-template-freemarker + demo-template-thymeleaf + demo-template-beetl + demo-template-enjoy + demo-orm-jdbctemplate + demo-orm-jpa + demo-orm-mybatis + demo-orm-mybatis-mapper-page + demo-orm-mybatis-plus + demo-orm-beetlsql + demo-upload + demo-cache-redis + demo-cache-ehcache + demo-email + demo-task + demo-task-quartz + demo-task-xxl-job + demo-swagger + demo-swagger-beauty + demo-rbac-security + demo-rbac-shiro + demo-session + demo-oauth + demo-social + demo-zookeeper + demo-mq-rabbitmq + demo-mq-rocketmq + demo-mq-kafka + demo-websocket + demo-websocket-socketio + demo-ureport2 + demo-uflo + demo-urule + demo-activiti + demo-async + demo-dubbo + demo-war + demo-elasticsearch + demo-mongodb + demo-neo4j + demo-docker + demo-multi-datasource-jpa + demo-multi-datasource-mybatis + demo-sharding-jdbc + demo-tio + demo-codegen + demo-graylog + demo-ldap + demo-dynamic-datasource + demo-ratelimit-guava + demo-ratelimit-redis + demo-elasticsearch-rest-high-level-client + demo-https + demo-flyway + demo-pay pom @@ -83,7 +84,7 @@ 1.8 2.1.0.RELEASE 8.0.21 - 5.4.1 + 5.4.5 29.0-jre 1.20 diff --git a/spring-boot-demo-activiti/pom.xml b/spring-boot-demo-activiti/pom.xml deleted file mode 100644 index 7c547e9..0000000 --- a/spring-boot-demo-activiti/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-activiti - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-activiti - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - org.activiti - activiti-spring-boot-starter - 7.1.0.M2 - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-activiti - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java b/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java deleted file mode 100644 index 0389381..0000000 --- a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/SpringBootDemoActivitiApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.activiti; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.activiti - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-03-31 22:24 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoActivitiApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoActivitiApplication.class, args); - } - -} - diff --git a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java b/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java deleted file mode 100644 index 3d9799f..0000000 --- a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/config/SecurityConfiguration.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.xkcoding.activiti.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * 安全配置类 - *

    - * - * @package: com.xkcoding.activiti.config - * @description: 安全配置类 - * @author: yangkai.shen - * @date: Created in 2019-07-01 18:40 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Configuration -public class SecurityConfiguration extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(userDetailsService()); - } - - @Bean - protected UserDetailsService myUserDetailsService() { - InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); - - String[][] usersGroupsAndRoles = {{"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"}, {"admin", "password", "ROLE_ACTIVITI_ADMIN"}}; - - for (String[] user : usersGroupsAndRoles) { - List authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); - log.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); - inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings - .stream() - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()))); - } - - - return inMemoryUserDetailsManager; - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } -} diff --git a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java b/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java deleted file mode 100755 index aa7897b..0000000 --- a/spring-boot-demo-activiti/src/main/java/com/xkcoding/activiti/util/SecurityUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.xkcoding.activiti.util; - -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Component; - -import java.util.Collection; - -/** - *

    - * 认证工具 - *

    - * - * @package: com.xkcoding.activiti.util - * @description: 认证工具 - * @author: yangkai.shen - * @date: Created in 2019-07-01 18:38 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class SecurityUtil { - - private final UserDetailsService userDetailsService; - - public void logInAs(String username) { - - UserDetails user = userDetailsService.loadUserByUsername(username); - if (user == null) { - throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); - } - - SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() { - @Override - public Collection getAuthorities() { - return user.getAuthorities(); - } - - @Override - public Object getCredentials() { - return user.getPassword(); - } - - @Override - public Object getDetails() { - return user; - } - - @Override - public Object getPrincipal() { - return user; - } - - @Override - public boolean isAuthenticated() { - return true; - } - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - - } - - @Override - public String getName() { - return user.getUsername(); - } - })); - org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); - } -} diff --git a/spring-boot-demo-actuator/pom.xml b/spring-boot-demo-actuator/pom.xml deleted file mode 100644 index 8906c77..0000000 --- a/spring-boot-demo-actuator/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-actuator - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-actuator - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.security - spring-security-test - test - - - - - spring-boot-demo-actuator - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java b/spring-boot-demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java deleted file mode 100644 index 6774035..0000000 --- a/spring-boot-demo-actuator/src/main/java/com/xkcoding/actuator/SpringBootDemoActuatorApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.actuator; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.actuator - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/29 2:27 PM - * @copyright: Copyright (c)2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoActuatorApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoActuatorApplication.class, args); - } -} diff --git a/spring-boot-demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java b/spring-boot-demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java deleted file mode 100644 index 4416b96..0000000 --- a/spring-boot-demo-actuator/src/test/java/com/xkcoding/actuator/SpringBootDemoActuatorApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.actuator; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoActuatorApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-admin/pom.xml b/spring-boot-demo-admin/pom.xml deleted file mode 100644 index 6d1db69..0000000 --- a/spring-boot-demo-admin/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - spring-boot-demo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-admin - pom - - - 2.1.0 - - - - spring-boot-demo-admin-client - spring-boot-demo-admin-server - - - - - - de.codecentric - spring-boot-admin-dependencies - ${spring-boot-admin.version} - pom - import - - - - - diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/pom.xml b/spring-boot-demo-admin/spring-boot-demo-admin-client/pom.xml deleted file mode 100644 index 53327a6..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-client/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-admin-client - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-admin-client - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo-admin - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - de.codecentric - spring-boot-admin-starter-client - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-admin-client - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java b/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java deleted file mode 100644 index 0b3527b..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.admin.client; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.admin.client - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/8 2:16 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoAdminClientApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoAdminClientApplication.class, args); - } -} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java b/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java deleted file mode 100644 index 3fd383f..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/main/java/com/xkcoding/admin/client/controller/IndexController.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.admin.client.controller; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 首页 - *

    - * - * @package: com.xkcoding.admin.client.controller - * @description: 首页 - * @author: yangkai.shen - * @date: Created in 2018/10/8 2:15 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -public class IndexController { - @GetMapping(value = {"", "/"}) - public String index() { - return "This is a Spring Boot Admin Client."; - } -} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java b/spring-boot-demo-admin/spring-boot-demo-admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java deleted file mode 100644 index 41b8cf8..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-client/src/test/java/com/xkcoding/admin/client/SpringBootDemoAdminClientApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.admin.client; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoAdminClientApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/README.md b/spring-boot-demo-admin/spring-boot-demo-admin-server/README.md deleted file mode 100644 index 9600eb8..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-server/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# spring-boot-demo-admin-server - -> 本 demo 主要演示了如何搭建一个 Spring Boot Admin 的服务端项目,可视化展示自己客户端项目的运行状态。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-admin-server - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-admin-server - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo-admin - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - de.codecentric - spring-boot-admin-starter-server - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-admin-server - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoAdminServerApplication.java - -```java -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.admin.server - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/8 2:08 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableAdminServer -@SpringBootApplication -public class SpringBootDemoAdminServerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoAdminServerApplication.class, args); - } -} -``` - -## application.yml - -```yaml -server: - port: 8000 -``` - diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/pom.xml b/spring-boot-demo-admin/spring-boot-demo-admin-server/pom.xml deleted file mode 100644 index 18b3050..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-server/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-admin-server - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-admin-server - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo-admin - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - de.codecentric - spring-boot-admin-starter-server - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-admin-server - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java b/spring-boot-demo-admin/spring-boot-demo-admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java deleted file mode 100644 index 1e2b8fb..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-server/src/main/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.admin.server; - -import de.codecentric.boot.admin.server.config.EnableAdminServer; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.admin.server - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/8 2:08 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableAdminServer -@SpringBootApplication -public class SpringBootDemoAdminServerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoAdminServerApplication.class, args); - } -} diff --git a/spring-boot-demo-admin/spring-boot-demo-admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java b/spring-boot-demo-admin/spring-boot-demo-admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java deleted file mode 100644 index b3df7ee..0000000 --- a/spring-boot-demo-admin/spring-boot-demo-admin-server/src/test/java/com/xkcoding/admin/server/SpringBootDemoAdminServerApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.admin.server; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoAdminServerApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-async/README.md b/spring-boot-demo-async/README.md deleted file mode 100644 index 3f46a9f..0000000 --- a/spring-boot-demo-async/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# spring-boot-demo-async - -> 此 demo 主要演示了 Spring Boot 如何使用原生提供的异步任务支持,实现异步执行任务。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-async - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-async - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-async - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -spring: - task: - execution: - pool: - # 最大线程数 - max-size: 16 - # 核心线程数 - core-size: 16 - # 存活时间 - keep-alive: 10s - # 队列大小 - queue-capacity: 100 - # 是否允许核心线程超时 - allow-core-thread-timeout: true - # 线程名称前缀 - thread-name-prefix: async-task- -``` - -## SpringBootDemoAsyncApplication.java - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.async - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableAsync -@SpringBootApplication -public class SpringBootDemoAsyncApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoAsyncApplication.class, args); - } - -} -``` - -## TaskFactory.java - -```java -/** - *

    - * 任务工厂 - *

    - * - * @package: com.xkcoding.async.task - * @description: 任务工厂 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class TaskFactory { - - /** - * 模拟5秒的异步任务 - */ - @Async - public Future asyncTask1() throws InterruptedException { - doTask("asyncTask1", 5); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟2秒的异步任务 - */ - @Async - public Future asyncTask2() throws InterruptedException { - doTask("asyncTask2", 2); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟3秒的异步任务 - */ - @Async - public Future asyncTask3() throws InterruptedException { - doTask("asyncTask3", 3); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟5秒的同步任务 - */ - public void task1() throws InterruptedException { - doTask("task1", 5); - } - - /** - * 模拟2秒的同步任务 - */ - public void task2() throws InterruptedException { - doTask("task2", 2); - } - - /** - * 模拟3秒的同步任务 - */ - public void task3() throws InterruptedException { - doTask("task3", 3); - } - - private void doTask(String taskName, Integer time) throws InterruptedException { - log.info("{}开始执行,当前线程名称【{}】", taskName, Thread.currentThread().getName()); - TimeUnit.SECONDS.sleep(time); - log.info("{}执行成功,当前线程名称【{}】", taskName, Thread.currentThread().getName()); - } -} -``` - -## TaskFactoryTest.java - -```java -/** - *

    - * 测试任务 - *

    - * - * @package: com.xkcoding.async.task - * @description: 测试任务 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class TaskFactoryTest extends SpringBootDemoAsyncApplicationTests { - @Autowired - private TaskFactory task; - - /** - * 测试异步任务 - */ - @Test - public void asyncTaskTest() throws InterruptedException, ExecutionException { - long start = System.currentTimeMillis(); - Future asyncTask1 = task.asyncTask1(); - Future asyncTask2 = task.asyncTask2(); - Future asyncTask3 = task.asyncTask3(); - - // 调用 get() 阻塞主线程 - asyncTask1.get(); - asyncTask2.get(); - asyncTask3.get(); - long end = System.currentTimeMillis(); - - log.info("异步任务全部执行结束,总耗时:{} 毫秒", (end - start)); - } - - /** - * 测试同步任务 - */ - @Test - public void taskTest() throws InterruptedException { - long start = System.currentTimeMillis(); - task.task1(); - task.task2(); - task.task3(); - long end = System.currentTimeMillis(); - - log.info("同步任务全部执行结束,总耗时:{} 毫秒", (end - start)); - } -} -``` - -## 运行结果 - -### 异步任务 - -```bash -2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-3] com.xkcoding.async.task.TaskFactory : asyncTask3开始执行,当前线程名称【async-task-3】 -2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-1] com.xkcoding.async.task.TaskFactory : asyncTask1开始执行,当前线程名称【async-task-1】 -2018-12-29 10:57:28.511 INFO 3134 --- [ async-task-2] com.xkcoding.async.task.TaskFactory : asyncTask2开始执行,当前线程名称【async-task-2】 -2018-12-29 10:57:30.514 INFO 3134 --- [ async-task-2] com.xkcoding.async.task.TaskFactory : asyncTask2执行成功,当前线程名称【async-task-2】 -2018-12-29 10:57:31.516 INFO 3134 --- [ async-task-3] com.xkcoding.async.task.TaskFactory : asyncTask3执行成功,当前线程名称【async-task-3】 -2018-12-29 10:57:33.517 INFO 3134 --- [ async-task-1] com.xkcoding.async.task.TaskFactory : asyncTask1执行成功,当前线程名称【async-task-1】 -2018-12-29 10:57:33.517 INFO 3134 --- [ main] com.xkcoding.async.task.TaskFactoryTest : 异步任务全部执行结束,总耗时:5015 毫秒 -``` - -### 同步任务 - -```bash -2018-12-29 10:55:49.830 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task1开始执行,当前线程名称【main】 -2018-12-29 10:55:54.834 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task1执行成功,当前线程名称【main】 -2018-12-29 10:55:54.835 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task2开始执行,当前线程名称【main】 -2018-12-29 10:55:56.839 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task2执行成功,当前线程名称【main】 -2018-12-29 10:55:56.839 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task3开始执行,当前线程名称【main】 -2018-12-29 10:55:59.843 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactory : task3执行成功,当前线程名称【main】 -2018-12-29 10:55:59.843 INFO 3079 --- [ main] com.xkcoding.async.task.TaskFactoryTest : 同步任务全部执行结束,总耗时:10023 毫秒 -``` - -## 参考 - -- Spring Boot 异步任务线程池的配置 参考官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-task-execution-scheduling \ No newline at end of file diff --git a/spring-boot-demo-async/pom.xml b/spring-boot-demo-async/pom.xml deleted file mode 100644 index c35e221..0000000 --- a/spring-boot-demo-async/pom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-async - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-async - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-async - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java b/spring-boot-demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java deleted file mode 100644 index 10eaa4a..0000000 --- a/spring-boot-demo-async/src/main/java/com/xkcoding/async/SpringBootDemoAsyncApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.async; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableAsync; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.async - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableAsync -@SpringBootApplication -public class SpringBootDemoAsyncApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoAsyncApplication.class, args); - } - -} - diff --git a/spring-boot-demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java b/spring-boot-demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java deleted file mode 100644 index 1cd30a7..0000000 --- a/spring-boot-demo-async/src/main/java/com/xkcoding/async/task/TaskFactory.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.xkcoding.async.task; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.AsyncResult; -import org.springframework.stereotype.Component; - -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - *

    - * 任务工厂 - *

    - * - * @package: com.xkcoding.async.task - * @description: 任务工厂 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class TaskFactory { - - /** - * 模拟5秒的异步任务 - */ - @Async - public Future asyncTask1() throws InterruptedException { - doTask("asyncTask1", 5); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟2秒的异步任务 - */ - @Async - public Future asyncTask2() throws InterruptedException { - doTask("asyncTask2", 2); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟3秒的异步任务 - */ - @Async - public Future asyncTask3() throws InterruptedException { - doTask("asyncTask3", 3); - return new AsyncResult<>(Boolean.TRUE); - } - - /** - * 模拟5秒的同步任务 - */ - public void task1() throws InterruptedException { - doTask("task1", 5); - } - - /** - * 模拟2秒的同步任务 - */ - public void task2() throws InterruptedException { - doTask("task2", 2); - } - - /** - * 模拟3秒的同步任务 - */ - public void task3() throws InterruptedException { - doTask("task3", 3); - } - - private void doTask(String taskName, Integer time) throws InterruptedException { - log.info("{}开始执行,当前线程名称【{}】", taskName, Thread.currentThread().getName()); - TimeUnit.SECONDS.sleep(time); - log.info("{}执行成功,当前线程名称【{}】", taskName, Thread.currentThread().getName()); - } -} diff --git a/spring-boot-demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java b/spring-boot-demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java deleted file mode 100644 index 326807b..0000000 --- a/spring-boot-demo-async/src/test/java/com/xkcoding/async/task/TaskFactoryTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.xkcoding.async.task; - -import com.xkcoding.async.SpringBootDemoAsyncApplicationTests; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -/** - *

    - * 测试任务 - *

    - * - * @package: com.xkcoding.async.task - * @description: 测试任务 - * @author: yangkai.shen - * @date: Created in 2018-12-29 10:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class TaskFactoryTest extends SpringBootDemoAsyncApplicationTests { - @Autowired - private TaskFactory task; - - /** - * 测试异步任务 - */ - @Test - public void asyncTaskTest() throws InterruptedException, ExecutionException { - long start = System.currentTimeMillis(); - Future asyncTask1 = task.asyncTask1(); - Future asyncTask2 = task.asyncTask2(); - Future asyncTask3 = task.asyncTask3(); - - // 调用 get() 阻塞主线程 - asyncTask1.get(); - asyncTask2.get(); - asyncTask3.get(); - long end = System.currentTimeMillis(); - - log.info("异步任务全部执行结束,总耗时:{} 毫秒", (end - start)); - } - - /** - * 测试同步任务 - */ - @Test - public void taskTest() throws InterruptedException { - long start = System.currentTimeMillis(); - task.task1(); - task.task2(); - task.task3(); - long end = System.currentTimeMillis(); - - log.info("同步任务全部执行结束,总耗时:{} 毫秒", (end - start)); - } -} \ No newline at end of file diff --git a/spring-boot-demo-cache-ehcache/README.md b/spring-boot-demo-cache-ehcache/README.md deleted file mode 100644 index dd071ba..0000000 --- a/spring-boot-demo-cache-ehcache/README.md +++ /dev/null @@ -1,301 +0,0 @@ -# spring-boot-demo-cache-ehcache - -> 此 demo 主要演示了 Spring Boot 如何集成 ehcache 使用缓存。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-cache-ehcache - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-cache-ehcache - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-cache - - - - net.sf.ehcache - ehcache - - - - org.projectlombok - lombok - true - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-cache-ehcache - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoCacheEhcacheApplication.java - -```java -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.cache.ehcache - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/11/16 17:02 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableCaching -public class SpringBootDemoCacheEhcacheApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args); - } -} -``` - -## application.yml - -```yaml -spring: - cache: - type: ehcache - ehcache: - config: classpath:ehcache.xml -logging: - level: - com.xkcoding: debug -``` - -## ehcache.xml - -```xml - - - - - - - - - - - -``` - -## UserServiceImpl.java - -```java -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.ehcache.service.impl - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - /** - * 模拟数据库 - */ - private static final Map DATABASES = Maps.newConcurrentMap(); - - /** - * 初始化数据 - */ - static { - DATABASES.put(1L, new User(1L, "user1")); - DATABASES.put(2L, new User(2L, "user2")); - DATABASES.put(3L, new User(3L, "user3")); - } - - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - @CachePut(value = "user", key = "#user.id") - @Override - public User saveOrUpdate(User user) { - DATABASES.put(user.getId(), user); - log.info("保存用户【user】= {}", user); - return user; - } - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - @Cacheable(value = "user", key = "#id") - @Override - public User get(Long id) { - // 我们假设从数据库读取 - log.info("查询用户【id】= {}", id); - return DATABASES.get(id); - } - - /** - * 删除 - * - * @param id key值 - */ - @CacheEvict(value = "user", key = "#id") - @Override - public void delete(Long id) { - DATABASES.remove(id); - log.info("删除用户【id】= {}", id); - } -} -``` - -## UserServiceTest.java - -```java -/** - *

    - * ehcache缓存测试 - *

    - * - * @package: com.xkcoding.cache.ehcache.service - * @description: ehcache缓存测试 - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoCacheEhcacheApplicationTests { - - @Autowired - private UserService userService; - - /** - * 获取两次,查看日志验证缓存 - */ - @Test - public void getTwice() { - // 模拟查询id为1的用户 - User user1 = userService.get(1L); - log.debug("【user1】= {}", user1); - - // 再次查询 - User user2 = userService.get(1L); - log.debug("【user2】= {}", user2); - // 查看日志,只打印一次日志,证明缓存生效 - } - - /** - * 先存,再查询,查看日志验证缓存 - */ - @Test - public void getAfterSave() { - userService.saveOrUpdate(new User(4L, "user4")); - - User user = userService.get(4L); - log.debug("【user】= {}", user); - // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 - } - - /** - * 测试删除,查看redis是否存在缓存数据 - */ - @Test - public void deleteUser() { - // 查询一次,使ehcache中存在缓存数据 - userService.get(1L); - // 删除,查看ehcache是否存在缓存数据 - userService.delete(1L); - } -} -``` - -## 参考 - -- Ehcache 官网:http://www.ehcache.org/documentation/ -- Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-caching-provider-ehcache2 -- 博客:https://juejin.im/post/5b308de9518825748b56ae1d \ No newline at end of file diff --git a/spring-boot-demo-cache-ehcache/pom.xml b/spring-boot-demo-cache-ehcache/pom.xml deleted file mode 100644 index 62f7e9b..0000000 --- a/spring-boot-demo-cache-ehcache/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-cache-ehcache - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-cache-ehcache - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-cache - - - - net.sf.ehcache - ehcache - - - - org.projectlombok - lombok - true - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-cache-ehcache - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java b/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java deleted file mode 100644 index 4b4f082..0000000 --- a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/SpringBootDemoCacheEhcacheApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.cache.ehcache; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cache.annotation.EnableCaching; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.cache.ehcache - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/11/16 17:02 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableCaching -public class SpringBootDemoCacheEhcacheApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoCacheEhcacheApplication.class, args); - } -} diff --git a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java b/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java deleted file mode 100644 index 20c8187..0000000 --- a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/entity/User.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.cache.ehcache.entity; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 用户实体 - *

    - * - * @package: com.xkcoding.cache.ehcache.entity - * @description: 用户实体 - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:53 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class User implements Serializable { - private static final long serialVersionUID = 2892248514883451461L; - /** - * 主键id - */ - private Long id; - /** - * 姓名 - */ - private String name; -} diff --git a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java b/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java deleted file mode 100644 index f607d02..0000000 --- a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/UserService.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.cache.ehcache.service; - -import com.xkcoding.cache.ehcache.entity.User; - -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.ehcache.service - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:53 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService { - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - User saveOrUpdate(User user); - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - User get(Long id); - - /** - * 删除 - * - * @param id key值 - */ - void delete(Long id); -} diff --git a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java b/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java deleted file mode 100644 index dca9949..0000000 --- a/spring-boot-demo-cache-ehcache/src/main/java/com/xkcoding/cache/ehcache/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.xkcoding.cache.ehcache.service.impl; - -import com.google.common.collect.Maps; -import com.xkcoding.cache.ehcache.entity.User; -import com.xkcoding.cache.ehcache.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -import java.util.Map; - -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.ehcache.service.impl - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - /** - * 模拟数据库 - */ - private static final Map DATABASES = Maps.newConcurrentMap(); - - /** - * 初始化数据 - */ - static { - DATABASES.put(1L, new User(1L, "user1")); - DATABASES.put(2L, new User(2L, "user2")); - DATABASES.put(3L, new User(3L, "user3")); - } - - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - @CachePut(value = "user", key = "#user.id") - @Override - public User saveOrUpdate(User user) { - DATABASES.put(user.getId(), user); - log.info("保存用户【user】= {}", user); - return user; - } - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - @Cacheable(value = "user", key = "#id") - @Override - public User get(Long id) { - // 我们假设从数据库读取 - log.info("查询用户【id】= {}", id); - return DATABASES.get(id); - } - - /** - * 删除 - * - * @param id key值 - */ - @CacheEvict(value = "user", key = "#id") - @Override - public void delete(Long id) { - DATABASES.remove(id); - log.info("删除用户【id】= {}", id); - } -} diff --git a/spring-boot-demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java b/spring-boot-demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java deleted file mode 100644 index 0720b54..0000000 --- a/spring-boot-demo-cache-ehcache/src/test/java/com/xkcoding/cache/ehcache/service/UserServiceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.cache.ehcache.service; - -import com.xkcoding.cache.ehcache.SpringBootDemoCacheEhcacheApplicationTests; -import com.xkcoding.cache.ehcache.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - *

    - * ehcache缓存测试 - *

    - * - * @package: com.xkcoding.cache.ehcache.service - * @description: ehcache缓存测试 - * @author: yangkai.shen - * @date: Created in 2018/11/16 16:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoCacheEhcacheApplicationTests { - - @Autowired - private UserService userService; - - /** - * 获取两次,查看日志验证缓存 - */ - @Test - public void getTwice() { - // 模拟查询id为1的用户 - User user1 = userService.get(1L); - log.debug("【user1】= {}", user1); - - // 再次查询 - User user2 = userService.get(1L); - log.debug("【user2】= {}", user2); - // 查看日志,只打印一次日志,证明缓存生效 - } - - /** - * 先存,再查询,查看日志验证缓存 - */ - @Test - public void getAfterSave() { - userService.saveOrUpdate(new User(4L, "user4")); - - User user = userService.get(4L); - log.debug("【user】= {}", user); - // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 - } - - /** - * 测试删除,查看redis是否存在缓存数据 - */ - @Test - public void deleteUser() { - // 查询一次,使ehcache中存在缓存数据 - userService.get(1L); - // 删除,查看ehcache是否存在缓存数据 - userService.delete(1L); - } -} \ No newline at end of file diff --git a/spring-boot-demo-cache-redis/README.md b/spring-boot-demo-cache-redis/README.md deleted file mode 100644 index 2f3d58e..0000000 --- a/spring-boot-demo-cache-redis/README.md +++ /dev/null @@ -1,367 +0,0 @@ -# spring-boot-demo-cache-redis - -> 此 demo 主要演示了 Spring Boot 如何整合 redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-cache-redis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-cache-redis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - - org.springframework.boot - spring-boot-starter-json - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.google.guava - guava - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-cache-redis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -spring: - redis: - host: localhost - # 连接超时时间(记得添加单位,Duration) - timeout: 10000ms - # Redis默认情况下有16个分片,这里配置具体使用的分片 - # database: 0 - lettuce: - pool: - # 连接池最大连接数(使用负值表示没有限制) 默认 8 - max-active: 8 - # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 - max-wait: -1ms - # 连接池中的最大空闲连接 默认 8 - max-idle: 8 - # 连接池中的最小空闲连接 默认 0 - min-idle: 0 - cache: - # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 - type: redis -logging: - level: - com.xkcoding: debug -``` - -## RedisConfig.java - -```java -/** - *

    - * redis配置 - *

    - * - * @package: com.xkcoding.cache.redis.config - * @description: redis配置 - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:41 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@AutoConfigureAfter(RedisAutoConfiguration.class) -@EnableCaching -public class RedisConfig { - - /** - * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 - */ - @Bean - public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - - /** - * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式 - */ - @Bean - public CacheManager cacheManager(RedisConnectionFactory factory) { - // 配置序列化 - RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); - RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); - - return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); - } -} -``` - -## UserServiceImpl.java - -```java -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.redis.service.impl - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - /** - * 模拟数据库 - */ - private static final Map DATABASES = Maps.newConcurrentMap(); - - /** - * 初始化数据 - */ - static { - DATABASES.put(1L, new User(1L, "user1")); - DATABASES.put(2L, new User(2L, "user2")); - DATABASES.put(3L, new User(3L, "user3")); - } - - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - @CachePut(value = "user", key = "#user.id") - @Override - public User saveOrUpdate(User user) { - DATABASES.put(user.getId(), user); - log.info("保存用户【user】= {}", user); - return user; - } - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - @Cacheable(value = "user", key = "#id") - @Override - public User get(Long id) { - // 我们假设从数据库读取 - log.info("查询用户【id】= {}", id); - return DATABASES.get(id); - } - - /** - * 删除 - * - * @param id key值 - */ - @CacheEvict(value = "user", key = "#id") - @Override - public void delete(Long id) { - DATABASES.remove(id); - log.info("删除用户【id】= {}", id); - } -} -``` - -## RedisTest.java - -> 主要测试使用 `RedisTemplate` 操作 `Redis` 中的数据: -> -> - opsForValue:对应 String(字符串) -> - opsForZSet:对应 ZSet(有序集合) -> - opsForHash:对应 Hash(哈希) -> - opsForList:对应 List(列表) -> - opsForSet:对应 Set(集合) -> - opsForGeo:** 对应 GEO(地理位置) - -```java -/** - *

    - * Redis测试 - *

    - * - * @package: com.xkcoding.cache.redis - * @description: Redis测试 - * @author: yangkai.shen - * @date: Created in 2018/11/15 17:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { - - @Autowired - private StringRedisTemplate stringRedisTemplate; - - @Autowired - private RedisTemplate redisCacheTemplate; - - /** - * 测试 Redis 操作 - */ - @Test - public void get() { - // 测试线程安全,程序结束查看redis中count的值是否为1000 - ExecutorService executorService = Executors.newFixedThreadPool(1000); - IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); - - stringRedisTemplate.opsForValue().set("k1", "v1"); - String k1 = stringRedisTemplate.opsForValue().get("k1"); - log.debug("【k1】= {}", k1); - - // 以下演示整合,具体Redis命令可以参考官方文档 - String key = "xkcoding:user:1"; - redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); - // 对应 String(字符串) - User user = (User) redisCacheTemplate.opsForValue().get(key); - log.debug("【user】= {}", user); - } -} - -``` - -## UserServiceTest.java - -> 主要测试使用Redis缓存是否起效 - -```java -/** - *

    - * Redis - 缓存测试 - *

    - * - * @package: com.xkcoding.cache.redis.service - * @description: Redis - 缓存测试 - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:53 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { - @Autowired - private UserService userService; - - /** - * 获取两次,查看日志验证缓存 - */ - @Test - public void getTwice() { - // 模拟查询id为1的用户 - User user1 = userService.get(1L); - log.debug("【user1】= {}", user1); - - // 再次查询 - User user2 = userService.get(1L); - log.debug("【user2】= {}", user2); - // 查看日志,只打印一次日志,证明缓存生效 - } - - /** - * 先存,再查询,查看日志验证缓存 - */ - @Test - public void getAfterSave() { - userService.saveOrUpdate(new User(4L, "测试中文")); - - User user = userService.get(4L); - log.debug("【user】= {}", user); - // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 - } - - /** - * 测试删除,查看redis是否存在缓存数据 - */ - @Test - public void deleteUser() { - // 查询一次,使redis中存在缓存数据 - userService.get(1L); - // 删除,查看redis是否存在缓存数据 - userService.delete(1L); - } - -} -``` - -## 参考 - -- spring-data-redis 官方文档:https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/ -- redis 文档:https://redis.io/documentation -- redis 中文文档:http://www.redis.cn/commands.html \ No newline at end of file diff --git a/spring-boot-demo-cache-redis/pom.xml b/spring-boot-demo-cache-redis/pom.xml deleted file mode 100644 index 01fcc38..0000000 --- a/spring-boot-demo-cache-redis/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-cache-redis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-cache-redis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - - org.springframework.boot - spring-boot-starter-json - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.google.guava - guava - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-cache-redis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java deleted file mode 100644 index da777d0..0000000 --- a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.xkcoding.cache.redis.config; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -import java.io.Serializable; - -/** - *

    - * redis配置 - *

    - * - * @package: com.xkcoding.cache.redis.config - * @description: redis配置 - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:41 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@AutoConfigureAfter(RedisAutoConfiguration.class) -@EnableCaching -public class RedisConfig { - - /** - * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 - */ - @Bean - public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - - /** - * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式 - */ - @Bean - public CacheManager cacheManager(RedisConnectionFactory factory) { - // 配置序列化 - RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); - RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); - - return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); - } -} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java deleted file mode 100644 index 3474dcb..0000000 --- a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.cache.redis.entity; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 用户实体 - *

    - * - * @package: com.xkcoding.cache.redis.entity - * @description: 用户实体 - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:39 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class User implements Serializable { - private static final long serialVersionUID = 2892248514883451461L; - /** - * 主键id - */ - private Long id; - /** - * 姓名 - */ - private String name; -} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java deleted file mode 100644 index 0da3c4a..0000000 --- a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.cache.redis.service; - -import com.xkcoding.cache.redis.entity.User; - -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.redis.service - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService { - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - User saveOrUpdate(User user); - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - User get(Long id); - - /** - * 删除 - * - * @param id key值 - */ - void delete(Long id); -} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java deleted file mode 100644 index 4942d0d..0000000 --- a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.xkcoding.cache.redis.service.impl; - -import com.google.common.collect.Maps; -import com.xkcoding.cache.redis.entity.User; -import com.xkcoding.cache.redis.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -import java.util.Map; - -/** - *

    - * UserService - *

    - * - * @package: com.xkcoding.cache.redis.service.impl - * @description: UserService - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - /** - * 模拟数据库 - */ - private static final Map DATABASES = Maps.newConcurrentMap(); - - /** - * 初始化数据 - */ - static { - DATABASES.put(1L, new User(1L, "user1")); - DATABASES.put(2L, new User(2L, "user2")); - DATABASES.put(3L, new User(3L, "user3")); - } - - /** - * 保存或修改用户 - * - * @param user 用户对象 - * @return 操作结果 - */ - @CachePut(value = "user", key = "#user.id") - @Override - public User saveOrUpdate(User user) { - DATABASES.put(user.getId(), user); - log.info("保存用户【user】= {}", user); - return user; - } - - /** - * 获取用户 - * - * @param id key值 - * @return 返回结果 - */ - @Cacheable(value = "user", key = "#id") - @Override - public User get(Long id) { - // 我们假设从数据库读取 - log.info("查询用户【id】= {}", id); - return DATABASES.get(id); - } - - /** - * 删除 - * - * @param id key值 - */ - @CacheEvict(value = "user", key = "#id") - @Override - public void delete(Long id) { - DATABASES.remove(id); - log.info("删除用户【id】= {}", id); - } -} diff --git a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java deleted file mode 100644 index 55e766f..0000000 --- a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.xkcoding.cache.redis; - -import com.xkcoding.cache.redis.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; - -import java.io.Serializable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.IntStream; - -/** - *

    - * Redis测试 - *

    - * - * @package: com.xkcoding.cache.redis - * @description: Redis测试 - * @author: yangkai.shen - * @date: Created in 2018/11/15 17:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { - - @Autowired - private StringRedisTemplate stringRedisTemplate; - - @Autowired - private RedisTemplate redisCacheTemplate; - - /** - * 测试 Redis 操作 - */ - @Test - public void get() { - // 测试线程安全,程序结束查看redis中count的值是否为1000 - ExecutorService executorService = Executors.newFixedThreadPool(1000); - IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); - - stringRedisTemplate.opsForValue().set("k1", "v1"); - String k1 = stringRedisTemplate.opsForValue().get("k1"); - log.debug("【k1】= {}", k1); - - // 以下演示整合,具体Redis命令可以参考官方文档 - String key = "xkcoding:user:1"; - redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); - // 对应 String(字符串) - User user = (User) redisCacheTemplate.opsForValue().get(key); - log.debug("【user】= {}", user); - } -} diff --git a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java deleted file mode 100644 index 6175948..0000000 --- a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.cache.redis.service; - -import com.xkcoding.cache.redis.SpringBootDemoCacheRedisApplicationTests; -import com.xkcoding.cache.redis.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - *

    - * Redis - 缓存测试 - *

    - * - * @package: com.xkcoding.cache.redis.service - * @description: Redis - 缓存测试 - * @author: yangkai.shen - * @date: Created in 2018/11/15 16:53 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { - @Autowired - private UserService userService; - - /** - * 获取两次,查看日志验证缓存 - */ - @Test - public void getTwice() { - // 模拟查询id为1的用户 - User user1 = userService.get(1L); - log.debug("【user1】= {}", user1); - - // 再次查询 - User user2 = userService.get(1L); - log.debug("【user2】= {}", user2); - // 查看日志,只打印一次日志,证明缓存生效 - } - - /** - * 先存,再查询,查看日志验证缓存 - */ - @Test - public void getAfterSave() { - userService.saveOrUpdate(new User(4L, "测试中文")); - - User user = userService.get(4L); - log.debug("【user】= {}", user); - // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 - } - - /** - * 测试删除,查看redis是否存在缓存数据 - */ - @Test - public void deleteUser() { - // 查询一次,使redis中存在缓存数据 - userService.get(1L); - // 删除,查看redis是否存在缓存数据 - userService.delete(1L); - } - -} diff --git a/spring-boot-demo-codegen/README.md b/spring-boot-demo-codegen/README.md deleted file mode 100644 index fd0a2ea..0000000 --- a/spring-boot-demo-codegen/README.md +++ /dev/null @@ -1,415 +0,0 @@ -# spring-boot-demo-codegen - -> 此 demo 主要演示了 Spring Boot 使用**模板技术**生成代码,并提供前端页面,可生成 Entity/Mapper/Service/Controller 等代码。 - -## 1. 主要功能 - -1. 使用 `velocity` 代码生成 -2. 暂时支持mysql数据库的代码生成 -3. 提供前端页面展示,并下载代码压缩包 - -> 注意:① Entity里使用lombok,简化代码 ② Mapper 和 Service 层集成 Mybatis-Plus 简化代码 - -## 2. 运行 - -1. 运行 `SpringBootDemoCodegenApplication` 启动项目 -2. 打开浏览器,输入 http://localhost:8080/demo/index.html -3. 输入查询条件,生成代码 - -## 3. 关键代码 - -### 3.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-codegen - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-codegen - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - org.springframework.boot - spring-boot-starter-undertow - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.zaxxer - HikariCP - - - - - org.apache.velocity - velocity - 1.7 - - - - org.apache.commons - commons-text - 1.6 - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-codegen - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 3.2. 代码生成器配置 - -```properties -#代码生成器,配置信息 -mainPath=com.xkcoding -#包名 -package=com.xkcoding -moduleName=generator -#作者 -author=Yangkai.Shen -#表前缀(类名不会包含表前缀) -tablePrefix=tb_ -#类型转换,配置信息 -tinyint=Integer -smallint=Integer -mediumint=Integer -int=Integer -integer=Integer -bigint=Long -float=Float -double=Double -decimal=BigDecimal -bit=Boolean -char=String -varchar=String -tinytext=String -text=String -mediumtext=String -longtext=String -date=LocalDateTime -datetime=LocalDateTime -timestamp=LocalDateTime -``` - -### 3.3. CodeGenUtil.java - -```java -/** - *

    - * 代码生成器 工具类 - *

    - * - * @package: com.xkcoding.codegen.utils - * @description: 代码生成器 工具类 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:27 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@UtilityClass -public class CodeGenUtil { - - private final String ENTITY_JAVA_VM = "Entity.java.vm"; - private final String MAPPER_JAVA_VM = "Mapper.java.vm"; - private final String SERVICE_JAVA_VM = "Service.java.vm"; - private final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm"; - private final String CONTROLLER_JAVA_VM = "Controller.java.vm"; - private final String MAPPER_XML_VM = "Mapper.xml.vm"; - private final String API_JS_VM = "api.js.vm"; - - private List getTemplates() { - List templates = new ArrayList<>(); - templates.add("template/Entity.java.vm"); - templates.add("template/Mapper.java.vm"); - templates.add("template/Mapper.xml.vm"); - templates.add("template/Service.java.vm"); - templates.add("template/ServiceImpl.java.vm"); - templates.add("template/Controller.java.vm"); - - templates.add("template/api.js.vm"); - return templates; - } - - /** - * 生成代码 - */ - public void generatorCode(GenConfig genConfig, Entity table, List columns, ZipOutputStream zip) { - //配置信息 - Props props = getConfig(); - boolean hasBigDecimal = false; - //表信息 - TableEntity tableEntity = new TableEntity(); - tableEntity.setTableName(table.getStr("tableName")); - - if (StrUtil.isNotBlank(genConfig.getComments())) { - tableEntity.setComments(genConfig.getComments()); - } else { - tableEntity.setComments(table.getStr("tableComment")); - } - - String tablePrefix; - if (StrUtil.isNotBlank(genConfig.getTablePrefix())) { - tablePrefix = genConfig.getTablePrefix(); - } else { - tablePrefix = props.getStr("tablePrefix"); - } - - //表名转换成Java类名 - String className = tableToJava(tableEntity.getTableName(), tablePrefix); - tableEntity.setCaseClassName(className); - tableEntity.setLowerClassName(StrUtil.lowerFirst(className)); - - //列信息 - List columnList = Lists.newArrayList(); - for (Entity column : columns) { - ColumnEntity columnEntity = new ColumnEntity(); - columnEntity.setColumnName(column.getStr("columnName")); - columnEntity.setDataType(column.getStr("dataType")); - columnEntity.setComments(column.getStr("columnComment")); - columnEntity.setExtra(column.getStr("extra")); - - //列名转换成Java属性名 - String attrName = columnToJava(columnEntity.getColumnName()); - columnEntity.setCaseAttrName(attrName); - columnEntity.setLowerAttrName(StrUtil.lowerFirst(attrName)); - - //列的数据类型,转换成Java类型 - String attrType = props.getStr(columnEntity.getDataType(), "unknownType"); - columnEntity.setAttrType(attrType); - if (!hasBigDecimal && "BigDecimal".equals(attrType)) { - hasBigDecimal = true; - } - //是否主键 - if ("PRI".equalsIgnoreCase(column.getStr("columnKey")) && tableEntity.getPk() == null) { - tableEntity.setPk(columnEntity); - } - - columnList.add(columnEntity); - } - tableEntity.setColumns(columnList); - - //没主键,则第一个字段为主键 - if (tableEntity.getPk() == null) { - tableEntity.setPk(tableEntity.getColumns().get(0)); - } - - //设置velocity资源加载器 - Properties prop = new Properties(); - prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - Velocity.init(prop); - //封装模板数据 - Map map = new HashMap<>(16); - map.put("tableName", tableEntity.getTableName()); - map.put("pk", tableEntity.getPk()); - map.put("className", tableEntity.getCaseClassName()); - map.put("classname", tableEntity.getLowerClassName()); - map.put("pathName", tableEntity.getLowerClassName().toLowerCase()); - map.put("columns", tableEntity.getColumns()); - map.put("hasBigDecimal", hasBigDecimal); - map.put("datetime", DateUtil.now()); - map.put("year", DateUtil.year(new Date())); - - if (StrUtil.isNotBlank(genConfig.getComments())) { - map.put("comments", genConfig.getComments()); - } else { - map.put("comments", tableEntity.getComments()); - } - - if (StrUtil.isNotBlank(genConfig.getAuthor())) { - map.put("author", genConfig.getAuthor()); - } else { - map.put("author", props.getStr("author")); - } - - if (StrUtil.isNotBlank(genConfig.getModuleName())) { - map.put("moduleName", genConfig.getModuleName()); - } else { - map.put("moduleName", props.getStr("moduleName")); - } - - if (StrUtil.isNotBlank(genConfig.getPackageName())) { - map.put("package", genConfig.getPackageName()); - map.put("mainPath", genConfig.getPackageName()); - } else { - map.put("package", props.getStr("package")); - map.put("mainPath", props.getStr("mainPath")); - } - VelocityContext context = new VelocityContext(map); - - //获取模板列表 - List templates = getTemplates(); - for (String template : templates) { - //渲染模板 - StringWriter sw = new StringWriter(); - Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8); - tpl.merge(context, sw); - - try { - //添加到zip - zip.putNextEntry(new ZipEntry(Objects.requireNonNull(getFileName(template, tableEntity.getCaseClassName(), map - .get("package") - .toString(), map.get("moduleName").toString())))); - IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString()); - IoUtil.close(sw); - zip.closeEntry(); - } catch (IOException e) { - throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e); - } - } - } - - - /** - * 列名转换成Java属性名 - */ - private String columnToJava(String columnName) { - return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", ""); - } - - /** - * 表名转换成Java类名 - */ - private String tableToJava(String tableName, String tablePrefix) { - if (StrUtil.isNotBlank(tablePrefix)) { - tableName = tableName.replaceFirst(tablePrefix, ""); - } - return columnToJava(tableName); - } - - /** - * 获取配置信息 - */ - private Props getConfig() { - Props props = new Props("generator.properties"); - props.autoLoad(true); - return props; - } - - /** - * 获取文件名 - */ - private String getFileName(String template, String className, String packageName, String moduleName) { - // 包路径 - String packagePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; - // 资源路径 - String resourcePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator; - // api路径 - String apiPath = GenConstants.SIGNATURE + File.separator + "api" + File.separator; - - if (StrUtil.isNotBlank(packageName)) { - packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator; - } - - if (template.contains(ENTITY_JAVA_VM)) { - return packagePath + "entity" + File.separator + className + ".java"; - } - - if (template.contains(MAPPER_JAVA_VM)) { - return packagePath + "mapper" + File.separator + className + "Mapper.java"; - } - - if (template.contains(SERVICE_JAVA_VM)) { - return packagePath + "service" + File.separator + className + "Service.java"; - } - - if (template.contains(SERVICE_IMPL_JAVA_VM)) { - return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; - } - - if (template.contains(CONTROLLER_JAVA_VM)) { - return packagePath + "controller" + File.separator + className + "Controller.java"; - } - - if (template.contains(MAPPER_XML_VM)) { - return resourcePath + "mapper" + File.separator + className + "Mapper.xml"; - } - - if (template.contains(API_JS_VM)) { - return apiPath + className.toLowerCase() + ".js"; - } - - return null; - } -} -``` - -### 3.4. 其余代码参见demo - -## 4. 演示 - - - -## 5. 参考 - -- [基于人人开源 自动构建项目_V1](https://qq343509740.gitee.io/2018/12/20/%E7%AC%94%E8%AE%B0/%E8%87%AA%E5%8A%A8%E6%9E%84%E5%BB%BA%E9%A1%B9%E7%9B%AE/%E5%9F%BA%E4%BA%8E%E4%BA%BA%E4%BA%BA%E5%BC%80%E6%BA%90%20%E8%87%AA%E5%8A%A8%E6%9E%84%E5%BB%BA%E9%A1%B9%E7%9B%AE_V1/) - -- [Mybatis-Plus代码生成器](https://mybatis.plus/guide/generator.html#%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96) \ No newline at end of file diff --git a/spring-boot-demo-codegen/pom.xml b/spring-boot-demo-codegen/pom.xml deleted file mode 100644 index d3605f7..0000000 --- a/spring-boot-demo-codegen/pom.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-codegen - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-codegen - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - org.springframework.boot - spring-boot-starter-undertow - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.zaxxer - HikariCP - - - - - org.apache.velocity - velocity-engine-core - 2.1 - - - - org.apache.commons - commons-text - 1.6 - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-codegen - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java deleted file mode 100644 index db828a7..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/SpringBootDemoCodegenApplication.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.codegen; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.codegen - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:10 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoCodegenApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoCodegenApplication.class, args); - } - -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java deleted file mode 100644 index 4f1c20f..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/IResultCode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.codegen.common; - -/** - *

    - * 统一状态码接口 - *

    - * - * @package: com.xkcoding.rbac.shiro.common - * @description: 统一状态码接口 - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:28 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface IResultCode { - /** - * 获取状态码 - * - * @return 状态码 - */ - Integer getCode(); - - /** - * 获取返回消息 - * - * @return 返回消息 - */ - String getMessage(); -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java deleted file mode 100644 index b06bfd3..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/PageResult.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.codegen.common; - -import lombok.AllArgsConstructor; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 分页结果集 - *

    - * - * @package: com.xkcoding.codegen.common - * @description: 分页结果集 - * @author: yangkai.shen - * @date: Created in 2019-03-22 11:24 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@AllArgsConstructor -public class PageResult { - /** - * 总条数 - */ - private Long total; - - /** - * 页码 - */ - private int pageNumber; - - /** - * 每页结果数 - */ - private int pageSize; - - /** - * 结果集 - */ - private List list; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java deleted file mode 100644 index 3a9cceb..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/R.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xkcoding.codegen.common; - -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - *

    - * 统一API对象返回 - *

    - * - * @package: com.xkcoding.codegen.common - * @description: 统一API对象返回 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:13 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -public class R { - /** - * 状态码 - */ - private Integer code; - - /** - * 返回消息 - */ - private String message; - - /** - * 状态 - */ - private boolean status; - - /** - * 返回数据 - */ - private T data; - - public R(Integer code, String message, boolean status, T data) { - this.code = code; - this.message = message; - this.status = status; - this.data = data; - } - - public R(IResultCode resultCode, boolean status, T data) { - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - this.status = status; - this.data = data; - } - - public R(IResultCode resultCode, boolean status) { - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - this.status = status; - this.data = null; - } - - public static R success() { - return new R<>(ResultCode.OK, true); - } - - public static R message(String message) { - return new R<>(ResultCode.OK.getCode(), message, true, null); - } - - public static R success(T data) { - return new R<>(ResultCode.OK, true, data); - } - - public static R fail() { - return new R<>(ResultCode.ERROR, false); - } - - public static R fail(IResultCode resultCode) { - return new R<>(resultCode, false); - } - - public static R fail(Integer code, String message) { - return new R<>(code, message, false, null); - } - - public static R fail(IResultCode resultCode, T data) { - return new R<>(resultCode, false, data); - } - - public static R fail(Integer code, String message, T data) { - return new R<>(code, message, false, data); - } - -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java deleted file mode 100644 index 3de00ad..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/common/ResultCode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.codegen.common; - -import lombok.Getter; - -/** - *

    - * 通用状态枚举 - *

    - * - * @package: com.xkcoding.codegen.common - * @description: 通用状态枚举 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:13 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public enum ResultCode implements IResultCode { - /** - * 成功 - */ - OK(200, "成功"), - /** - * 失败 - */ - ERROR(500, "失败"); - - /** - * 返回码 - */ - private Integer code; - - /** - * 返回消息 - */ - private String message; - - ResultCode(Integer code, String message) { - this.code = code; - this.message = message; - } - -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java deleted file mode 100644 index 599373b..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/constants/GenConstants.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.codegen.constants; - -/** - *

    - * 常量池 - *

    - * - * @package: com.xkcoding.codegen.constants - * @description: 常量池 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:04 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface GenConstants { - /** - * 签名 - */ - String SIGNATURE = "xkcoding代码生成"; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java deleted file mode 100755 index 841ea4d..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/controller/CodeGenController.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.xkcoding.codegen.controller; - -import cn.hutool.core.io.IoUtil; -import com.xkcoding.codegen.common.R; -import com.xkcoding.codegen.entity.GenConfig; -import com.xkcoding.codegen.entity.TableRequest; -import com.xkcoding.codegen.service.CodeGenService; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import org.springframework.http.HttpHeaders; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletResponse; - -/** - *

    - * 代码生成器 - *

    - * - * @package: com.xkcoding.codegen.controller - * @description: 代码生成器 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:11 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@AllArgsConstructor -@RequestMapping("/generator") -public class CodeGenController { - private final CodeGenService codeGenService; - - /** - * 列表 - * - * @param request 参数集 - * @return 数据库表 - */ - @GetMapping("/table") - public R listTables(TableRequest request) { - return R.success(codeGenService.listTables(request)); - } - - /** - * 生成代码 - */ - @SneakyThrows - @PostMapping("") - public void generatorCode(@RequestBody GenConfig genConfig, HttpServletResponse response) { - byte[] data = codeGenService.generatorCode(genConfig); - - response.reset(); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s.zip", genConfig.getTableName())); - response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length)); - response.setContentType("application/octet-stream; charset=UTF-8"); - - IoUtil.write(response.getOutputStream(), Boolean.TRUE, data); - } -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java deleted file mode 100755 index a2a0bb3..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/ColumnEntity.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.xkcoding.codegen.entity; - -import lombok.Data; - -/** - *

    - * 列属性: https://blog.csdn.net/lkforce/article/details/79557482 - *

    - * - * @package: com.xkcoding.codegen.entity - * @description: 列属性: https://blog.csdn.net/lkforce/article/details/79557482 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:46 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ColumnEntity { - /** - * 列表 - */ - private String columnName; - /** - * 数据类型 - */ - private String dataType; - /** - * 备注 - */ - private String comments; - /** - * 驼峰属性 - */ - private String caseAttrName; - /** - * 普通属性 - */ - private String lowerAttrName; - /** - * 属性类型 - */ - private String attrType; - /** - * jdbc类型 - */ - private String jdbcType; - /** - * 其他信息 - */ - private String extra; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java deleted file mode 100644 index 7e1cd0c..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/GenConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.codegen.entity; - -import lombok.Data; - -/** - *

    - * 生成配置 - *

    - * - * @package: com.xkcoding.codegen.entity - * @description: 生成配置 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:47 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class GenConfig { - /** - * 请求参数 - */ - private TableRequest request; - /** - * 包名 - */ - private String packageName; - /** - * 作者 - */ - private String author; - /** - * 模块名称 - */ - private String moduleName; - /** - * 表前缀 - */ - private String tablePrefix; - /** - * 表名称 - */ - private String tableName; - /** - * 表备注 - */ - private String comments; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java deleted file mode 100755 index 4786726..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableEntity.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.xkcoding.codegen.entity; - -import lombok.Data; - -import java.util.List; - -/** - *

    - * 表属性: https://blog.csdn.net/lkforce/article/details/79557482 - *

    - * - * @package: com.xkcoding.codegen.entity - * @description: 表属性: https://blog.csdn.net/lkforce/article/details/79557482 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:47 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class TableEntity { - /** - * 名称 - */ - private String tableName; - /** - * 备注 - */ - private String comments; - /** - * 主键 - */ - private ColumnEntity pk; - /** - * 列名 - */ - private List columns; - /** - * 驼峰类型 - */ - private String caseClassName; - /** - * 普通类型 - */ - private String lowerClassName; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java deleted file mode 100644 index 7f086c2..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/entity/TableRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.codegen.entity; - -import lombok.Data; - -/** - *

    - * 表格请求参数 - *

    - * - * @package: com.xkcoding.codegen.entity - * @description: 表格请求参数 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:24 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class TableRequest { - /** - * 当前页 - */ - private Integer currentPage; - /** - * 每页条数 - */ - private Integer pageSize; - /** - * jdbc-前缀 - */ - private String prepend; - /** - * jdbc-url - */ - private String url; - /** - * 用户名 - */ - private String username; - /** - * 密码 - */ - private String password; - /** - * 表名 - */ - private String tableName; -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java deleted file mode 100644 index 7123db8..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/CodeGenService.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.codegen.service; - -import cn.hutool.db.Entity; -import com.xkcoding.codegen.common.PageResult; -import com.xkcoding.codegen.entity.GenConfig; -import com.xkcoding.codegen.entity.TableRequest; - -/** - *

    - * 代码生成器 - *

    - * - * @package: com.xkcoding.codegen.service - * @description: 代码生成器 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:15 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface CodeGenService { - /** - * 生成代码 - * - * @param genConfig 生成配置 - * @return 代码压缩文件 - */ - byte[] generatorCode(GenConfig genConfig); - - /** - * 分页查询表信息 - * - * @param request 请求参数 - * @return 表名分页信息 - */ - PageResult listTables(TableRequest request); -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java deleted file mode 100755 index 818278e..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/service/impl/CodeGenServiceImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.xkcoding.codegen.service.impl; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Db; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import com.xkcoding.codegen.common.PageResult; -import com.xkcoding.codegen.entity.GenConfig; -import com.xkcoding.codegen.entity.TableRequest; -import com.xkcoding.codegen.service.CodeGenService; -import com.xkcoding.codegen.utils.CodeGenUtil; -import com.xkcoding.codegen.utils.DbUtil; -import com.zaxxer.hikari.HikariDataSource; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import org.springframework.stereotype.Service; - -import java.io.ByteArrayOutputStream; -import java.math.BigDecimal; -import java.util.List; -import java.util.zip.ZipOutputStream; - -/** - *

    - * 代码生成器 - *

    - * - * @package: com.xkcoding.codegen.service.impl - * @description: 代码生成器 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:15 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@AllArgsConstructor -public class CodeGenServiceImpl implements CodeGenService { - private final String TABLE_SQL_TEMPLATE = "select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables where table_schema = (select database()) %s order by create_time desc"; - - private final String COLUMN_SQL_TEMPLATE = "select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns where table_name = ? and table_schema = (select database()) order by ordinal_position"; - - private final String COUNT_SQL_TEMPLATE = "select count(1) from (%s)tmp"; - - private final String PAGE_SQL_TEMPLATE = " limit ?,?"; - - /** - * 分页查询表信息 - * - * @param request 请求参数 - * @return 表名分页信息 - */ - @Override - @SneakyThrows - public PageResult listTables(TableRequest request) { - HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); - Db db = new Db(dataSource); - - Page page = new Page(request.getCurrentPage(), request.getPageSize()); - int start = page.getStartPosition(); - int pageSize = page.getPageSize(); - - String paramSql = StrUtil.EMPTY; - if (StrUtil.isNotBlank(request.getTableName())) { - paramSql = "and table_name like concat('%', ?, '%')"; - } - String sql = String.format(TABLE_SQL_TEMPLATE, paramSql); - String countSql = String.format(COUNT_SQL_TEMPLATE, sql); - - List query; - BigDecimal count; - if (StrUtil.isNotBlank(request.getTableName())) { - query = db.query(sql + PAGE_SQL_TEMPLATE, request.getTableName(), start, pageSize); - count = (BigDecimal) db.queryNumber(countSql, request.getTableName()); - } else { - query = db.query(sql + PAGE_SQL_TEMPLATE, start, pageSize); - count = (BigDecimal) db.queryNumber(countSql); - } - - PageResult pageResult = new PageResult<>(count.longValue(), page.getPageNumber(), page.getPageSize(), query); - - dataSource.close(); - return pageResult; - } - - /** - * 生成代码 - * - * @param genConfig 生成配置 - * @return 代码压缩文件 - */ - @Override - public byte[] generatorCode(GenConfig genConfig) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - ZipOutputStream zip = new ZipOutputStream(outputStream); - - //查询表信息 - Entity table = queryTable(genConfig.getRequest()); - //查询列信息 - List columns = queryColumns(genConfig.getRequest()); - //生成代码 - CodeGenUtil.generatorCode(genConfig, table, columns, zip); - IoUtil.close(zip); - return outputStream.toByteArray(); - } - - @SneakyThrows - private Entity queryTable(TableRequest request) { - HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); - Db db = new Db(dataSource); - - String paramSql = StrUtil.EMPTY; - if (StrUtil.isNotBlank(request.getTableName())) { - paramSql = "and table_name = ?"; - } - String sql = String.format(TABLE_SQL_TEMPLATE, paramSql); - Entity entity = db.queryOne(sql, request.getTableName()); - - dataSource.close(); - return entity; - } - - @SneakyThrows - private List queryColumns(TableRequest request) { - HikariDataSource dataSource = DbUtil.buildFromTableRequest(request); - Db db = new Db(dataSource); - - List query = db.query(COLUMN_SQL_TEMPLATE, request.getTableName()); - - dataSource.close(); - return query; - } - -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java deleted file mode 100644 index ef11a63..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/CodeGenUtil.java +++ /dev/null @@ -1,271 +0,0 @@ -package com.xkcoding.codegen.utils; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.setting.dialect.Props; -import com.google.common.collect.Lists; -import com.xkcoding.codegen.constants.GenConstants; -import com.xkcoding.codegen.entity.ColumnEntity; -import com.xkcoding.codegen.entity.GenConfig; -import com.xkcoding.codegen.entity.TableEntity; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.text.WordUtils; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; - -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -/** - *

    - * 代码生成器 工具类 - *

    - * - * @package: com.xkcoding.codegen.utils - * @description: 代码生成器 工具类 - * @author: yangkai.shen - * @date: Created in 2019-03-22 09:27 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@UtilityClass -public class CodeGenUtil { - - private final String ENTITY_JAVA_VM = "Entity.java.vm"; - private final String MAPPER_JAVA_VM = "Mapper.java.vm"; - private final String SERVICE_JAVA_VM = "Service.java.vm"; - private final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm"; - private final String CONTROLLER_JAVA_VM = "Controller.java.vm"; - private final String MAPPER_XML_VM = "Mapper.xml.vm"; - private final String API_JS_VM = "api.js.vm"; - - private List getTemplates() { - List templates = new ArrayList<>(); - templates.add("template/Entity.java.vm"); - templates.add("template/Mapper.java.vm"); - templates.add("template/Mapper.xml.vm"); - templates.add("template/Service.java.vm"); - templates.add("template/ServiceImpl.java.vm"); - templates.add("template/Controller.java.vm"); - - templates.add("template/api.js.vm"); - return templates; - } - - /** - * 生成代码 - */ - public void generatorCode(GenConfig genConfig, Entity table, List columns, ZipOutputStream zip) { - //配置信息 - Props propsDB2Java = getConfig("generator.properties"); - Props propsDB2Jdbc = getConfig("jdbc_type.properties"); - - boolean hasBigDecimal = false; - //表信息 - TableEntity tableEntity = new TableEntity(); - tableEntity.setTableName(table.getStr("tableName")); - - if (StrUtil.isNotBlank(genConfig.getComments())) { - tableEntity.setComments(genConfig.getComments()); - } else { - tableEntity.setComments(table.getStr("tableComment")); - } - - String tablePrefix; - if (StrUtil.isNotBlank(genConfig.getTablePrefix())) { - tablePrefix = genConfig.getTablePrefix(); - } else { - tablePrefix = propsDB2Java.getStr("tablePrefix"); - } - - //表名转换成Java类名 - String className = tableToJava(tableEntity.getTableName(), tablePrefix); - tableEntity.setCaseClassName(className); - tableEntity.setLowerClassName(StrUtil.lowerFirst(className)); - - //列信息 - List columnList = Lists.newArrayList(); - for (Entity column : columns) { - ColumnEntity columnEntity = new ColumnEntity(); - columnEntity.setColumnName(column.getStr("columnName")); - columnEntity.setDataType(column.getStr("dataType")); - columnEntity.setComments(column.getStr("columnComment")); - columnEntity.setExtra(column.getStr("extra")); - - //列名转换成Java属性名 - String attrName = columnToJava(columnEntity.getColumnName()); - columnEntity.setCaseAttrName(attrName); - columnEntity.setLowerAttrName(StrUtil.lowerFirst(attrName)); - - //列的数据类型,转换成Java类型 - String attrType = propsDB2Java.getStr(columnEntity.getDataType(), "unknownType"); - columnEntity.setAttrType(attrType); - String jdbcType = propsDB2Jdbc.getStr(columnEntity.getDataType(), "unknownType"); - columnEntity.setJdbcType(jdbcType); - if (!hasBigDecimal && "BigDecimal".equals(attrType)) { - hasBigDecimal = true; - } - //是否主键 - if ("PRI".equalsIgnoreCase(column.getStr("columnKey")) && tableEntity.getPk() == null) { - tableEntity.setPk(columnEntity); - } - - columnList.add(columnEntity); - } - tableEntity.setColumns(columnList); - - //没主键,则第一个字段为主键 - if (tableEntity.getPk() == null) { - tableEntity.setPk(tableEntity.getColumns().get(0)); - } - - //设置velocity资源加载器 - Properties prop = new Properties(); - prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - Velocity.init(prop); - //封装模板数据 - Map map = new HashMap<>(16); - map.put("tableName", tableEntity.getTableName()); - map.put("pk", tableEntity.getPk()); - map.put("className", tableEntity.getCaseClassName()); - map.put("classname", tableEntity.getLowerClassName()); - map.put("pathName", tableEntity.getLowerClassName().toLowerCase()); - map.put("columns", tableEntity.getColumns()); - map.put("hasBigDecimal", hasBigDecimal); - map.put("datetime", DateUtil.now()); - map.put("year", DateUtil.year(new Date())); - - if (StrUtil.isNotBlank(genConfig.getComments())) { - map.put("comments", genConfig.getComments()); - } else { - map.put("comments", tableEntity.getComments()); - } - - if (StrUtil.isNotBlank(genConfig.getAuthor())) { - map.put("author", genConfig.getAuthor()); - } else { - map.put("author", propsDB2Java.getStr("author")); - } - - if (StrUtil.isNotBlank(genConfig.getModuleName())) { - map.put("moduleName", genConfig.getModuleName()); - } else { - map.put("moduleName", propsDB2Java.getStr("moduleName")); - } - - if (StrUtil.isNotBlank(genConfig.getPackageName())) { - map.put("package", genConfig.getPackageName()); - map.put("mainPath", genConfig.getPackageName()); - } else { - map.put("package", propsDB2Java.getStr("package")); - map.put("mainPath", propsDB2Java.getStr("mainPath")); - } - VelocityContext context = new VelocityContext(map); - - //获取模板列表 - List templates = getTemplates(); - for (String template : templates) { - //渲染模板 - StringWriter sw = new StringWriter(); - Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8); - tpl.merge(context, sw); - - try { - //添加到zip - zip.putNextEntry(new ZipEntry(Objects.requireNonNull(getFileName(template, tableEntity.getCaseClassName(), map - .get("package") - .toString(), map.get("moduleName").toString())))); - IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString()); - IoUtil.close(sw); - zip.closeEntry(); - } catch (IOException e) { - throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e); - } - } - } - - - /** - * 列名转换成Java属性名 - */ - private String columnToJava(String columnName) { - return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", ""); - } - - /** - * 表名转换成Java类名 - */ - private String tableToJava(String tableName, String tablePrefix) { - if (StrUtil.isNotBlank(tablePrefix)) { - tableName = tableName.replaceFirst(tablePrefix, ""); - } - return columnToJava(tableName); - } - - /** - * 获取配置信息 - */ - private Props getConfig(String fileName) { - Props props = new Props(fileName); - props.autoLoad(true); - return props; - } - - /** - * 获取文件名 - */ - private String getFileName(String template, String className, String packageName, String moduleName) { - // 包路径 - String packagePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator; - // 资源路径 - String resourcePath = GenConstants.SIGNATURE + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator; - // api路径 - String apiPath = GenConstants.SIGNATURE + File.separator + "api" + File.separator; - - if (StrUtil.isNotBlank(packageName)) { - packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator; - } - - if (template.contains(ENTITY_JAVA_VM)) { - return packagePath + "entity" + File.separator + className + ".java"; - } - - if (template.contains(MAPPER_JAVA_VM)) { - return packagePath + "mapper" + File.separator + className + "Mapper.java"; - } - - if (template.contains(SERVICE_JAVA_VM)) { - return packagePath + "service" + File.separator + className + "Service.java"; - } - - if (template.contains(SERVICE_IMPL_JAVA_VM)) { - return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; - } - - if (template.contains(CONTROLLER_JAVA_VM)) { - return packagePath + "controller" + File.separator + className + "Controller.java"; - } - - if (template.contains(MAPPER_XML_VM)) { - return resourcePath + "mapper" + File.separator + className + "Mapper.xml"; - } - - if (template.contains(API_JS_VM)) { - return apiPath + className.toLowerCase() + ".js"; - } - - return null; - } -} diff --git a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java b/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java deleted file mode 100644 index 2d6fb69..0000000 --- a/spring-boot-demo-codegen/src/main/java/com/xkcoding/codegen/utils/DbUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.codegen.utils; - -import com.xkcoding.codegen.entity.TableRequest; -import com.zaxxer.hikari.HikariDataSource; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; - -/** - *

    - * 数据库工具类 - *

    - * - * @package: com.xkcoding.codegen.utils - * @description: 数据库工具类 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:26 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@UtilityClass -public class DbUtil { - public HikariDataSource buildFromTableRequest(TableRequest request) { - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setJdbcUrl(request.getPrepend() + request.getUrl()); - dataSource.setUsername(request.getUsername()); - dataSource.setPassword(request.getPassword()); - return dataSource; - } - -} diff --git a/spring-boot-demo-codegen/src/main/resources/logback-spring.xml b/spring-boot-demo-codegen/src/main/resources/logback-spring.xml deleted file mode 100644 index 5b24b88..0000000 --- a/spring-boot-demo-codegen/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - INFO - - - ${CONSOLE_LOG_PATTERN} - UTF-8 - - - - - - - - ERROR - - DENY - - ACCEPT - - - - - - - logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - - - 2MB - - - - - - - ${FILE_LOG_PATTERN} - UTF-8 - - - - - - - Error - - - - - - - logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - 2MB - - - - ${FILE_ERROR_PATTERN} - UTF-8 - - - - - - - - - diff --git a/spring-boot-demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js b/spring-boot-demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js deleted file mode 100644 index 68c2c43..0000000 --- a/spring-boot-demo-codegen/src/main/resources/static/libs/datejs/date-zh-CN.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * @version: 1.0 Alpha-1 - * @author: Coolite Inc. http://www.coolite.com/ - * @date: 2008-05-13 - * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved. - * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. - * @website: http://www.datejs.com/ - */ -Date.CultureInfo={name:"zh-CN",englishName:"Chinese (People's Republic of China)",nativeName:"中文(中华人民共和国)",dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],abbreviatedDayNames:["日","一","二","三","四","五","六"],shortestDayNames:["日","一","二","三","四","五","六"],firstLetterDayNames:["日","一","二","三","四","五","六"],monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],abbreviatedMonthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],amDesignator:"上午",pmDesignator:"下午",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"ymd",formatPatterns:{shortDate:"yyyy/M/d",longDate:"yyyy'年'M'月'd'日'",shortTime:"H:mm",longTime:"H:mm:ss",fullDateTime:"yyyy'年'M'月'd'日' H:mm:ss",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"M'月'd'日'",yearMonth:"yyyy'年'M'月'"},regexPatterns:{jan:/^一月/i,feb:/^二月/i,mar:/^三月/i,apr:/^四月/i,may:/^五月/i,jun:/^六月/i,jul:/^七月/i,aug:/^八月/i,sep:/^九月/i,oct:/^十月/i,nov:/^十一月/i,dec:/^十二月/i,sun:/^星期日/i,mon:/^星期一/i,tue:/^星期二/i,wed:/^星期三/i,thu:/^星期四/i,fri:/^星期五/i,sat:/^星期六/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]}; -(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;} -return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;} -var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);} -if(x.seconds){this.addSeconds(x.seconds);} -if(x.minutes){this.addMinutes(x.minutes);} -if(x.hours){this.addHours(x.hours);} -if(x.weeks){this.addWeeks(x.weeks);} -if(x.months){this.addMonths(x.months);} -if(x.years){this.addYears(x.years);} -if(x.days){this.addDays(x.days);} -return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;} -g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;} -$y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(nmax){throw new RangeError(n+" is not a valid value for "+name+".");} -return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());} -if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());} -if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());} -if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());} -if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());} -if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());} -if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());} -if(config.timezone){this.setTimezone(config.timezone);} -if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);} -if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);} -return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;} -else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);} -return this;} -return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;} -return'"'+this.getUTCFullYear()+'-'+ -f(this.getUTCMonth()+1)+'-'+ -f(this.getUTCDate())+'T'+ -f(this.getUTCHours())+':'+ -f(this.getUTCMinutes())+':'+ -f(this.getUTCSeconds())+'Z"';};} -$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}} -var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");} -x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}()); -(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());} -return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;itemp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");} -return this;} -return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;} -return t.addDays(shift);};};for(var i=0;i-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;} -if(k==v){break;}} -return true;} -if(j.substring(j.length-1)!="s"){j+="s";} -return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} -if(!last&&q[1].length===0){last=true;} -if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} -if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} -if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} -var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} -return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} -for(var i=0;i - * ${comments} - *

    - * - * @package: ${package}.${moduleName}.controller - * @description: ${comments} - * @author: ${author} - * @date: Created in ${datetime} - * @copyright: Copyright (c) ${year} - * @version: V1.0 - * @modified: ${author} - */ -@Slf4j -@RestController -@RequestMapping("/${pathName}") -@Api(description = "${className}Controller", tags = {"${comments}"}) -public class ${className}Controller { - @Autowired - private ${className}Service ${classname}Service; - - /** - * 分页查询${comments} - * @param page 分页对象 - * @param ${classname} ${comments} - * @return R - */ - @GetMapping("") - @ApiOperation(value = "分页查询${comments}", notes = "分页查询${comments}") - @ApiImplicitParams({ - @ApiImplicitParam(name = "page", value = "分页参数", required = true), - @ApiImplicitParam(name = "${classname}", value = "查询条件", required = true) - }) - public R list${className}(Page page, ${className} ${classname}) { - return R.success(${classname}Service.page(page,Wrappers.query(${classname}))); - } - - - /** - * 通过id查询${comments} - * @param ${pk.lowerAttrName} id - * @return R - */ - @GetMapping("/{${pk.lowerAttrName}}") - @ApiOperation(value = "通过id查询${comments}", notes = "通过id查询${comments}") - @ApiImplicitParams({ - @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) - }) - public R get${className}(@PathVariable("${pk.lowerAttrName}") ${pk.attrType} ${pk.lowerAttrName}){ - return R.success(${classname}Service.getById(${pk.lowerAttrName})); - } - - /** - * 新增${comments} - * @param ${classname} ${comments} - * @return R - */ - @PostMapping - @ApiOperation(value = "新增${comments}", notes = "新增${comments}") - public R save${className}(@RequestBody ${className} ${classname}){ - return R.success(${classname}Service.save(${classname})); - } - - /** - * 修改${comments} - * @param ${pk.lowerAttrName} id - * @param ${classname} ${comments} - * @return R - */ - @PutMapping("/{${pk.lowerAttrName}}") - @ApiOperation(value = "修改${comments}", notes = "修改${comments}") - @ApiImplicitParams({ - @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) - }) - public R update${className}(@PathVariable ${pk.attrType} ${pk.lowerAttrName}, @RequestBody ${className} ${classname}){ - return R.success(${classname}Service.updateById(${classname})); - } - - /** - * 通过id删除${comments} - * @param ${pk.lowerAttrName} id - * @return R - */ - @DeleteMapping("/{${pk.lowerAttrName}}") - @ApiOperation(value = "删除${comments}", notes = "删除${comments}") - @ApiImplicitParams({ - @ApiImplicitParam(name = "${pk.lowerAttrName}", value = "主键id", required = true) - }) - public R delete${className}(@PathVariable ${pk.attrType} ${pk.lowerAttrName}){ - return R.success(${classname}Service.removeById(${pk.lowerAttrName})); - } - -} diff --git a/spring-boot-demo-codegen/src/main/resources/template/Entity.java.vm b/spring-boot-demo-codegen/src/main/resources/template/Entity.java.vm deleted file mode 100755 index ddf3bf0..0000000 --- a/spring-boot-demo-codegen/src/main/resources/template/Entity.java.vm +++ /dev/null @@ -1,47 +0,0 @@ -package ${package}.${moduleName}.entity; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.activerecord.Model; -import lombok.Data; -import lombok.EqualsAndHashCode; -#if(${hasBigDecimal}) -import java.math.BigDecimal; -#end -import java.time.LocalDateTime; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.NoArgsConstructor; -/** - *

    - * ${comments} - *

    - * - * @package: ${package}.${moduleName}.entity - * @description: ${comments} - * @author: ${author} - * @date: Created in ${datetime} - * @copyright: Copyright (c) ${year} - * @version: V1.0 - * @modified: ${author} - */ -@Data -@NoArgsConstructor -@TableName("${tableName}") -@ApiModel(description = "${comments}") -@EqualsAndHashCode(callSuper = true) -public class ${className} extends Model<${className}> { - private static final long serialVersionUID = 1L; - - #foreach ($column in $columns) - /** - * $column.comments - */ - #if($column.columnName == $pk.columnName) - @TableId - #end - @ApiModelProperty(value = "$column.comments") - private $column.attrType $column.lowerAttrName; - #end - -} diff --git a/spring-boot-demo-codegen/src/main/resources/template/Mapper.java.vm b/spring-boot-demo-codegen/src/main/resources/template/Mapper.java.vm deleted file mode 100755 index 7415cb8..0000000 --- a/spring-boot-demo-codegen/src/main/resources/template/Mapper.java.vm +++ /dev/null @@ -1,23 +0,0 @@ -package ${package}.${moduleName}.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.springframework.stereotype.Component; -import ${package}.${moduleName}.entity.${className}; - -/** - *

    - * ${comments} - *

    - * - * @package: ${package}.${moduleName}.mapper - * @description: ${comments} - * @author: ${author} - * @date: Created in ${datetime} - * @copyright: Copyright (c) ${year} - * @version: V1.0 - * @modified: ${author} - */ -@Component -public interface ${className}Mapper extends BaseMapper<${className}> { - -} diff --git a/spring-boot-demo-codegen/src/main/resources/template/Service.java.vm b/spring-boot-demo-codegen/src/main/resources/template/Service.java.vm deleted file mode 100755 index 028598f..0000000 --- a/spring-boot-demo-codegen/src/main/resources/template/Service.java.vm +++ /dev/null @@ -1,21 +0,0 @@ -package ${package}.${moduleName}.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import ${package}.${moduleName}.entity.${className}; - -/** - *

    - * ${comments} - *

    - * - * @package: ${package}.${moduleName}.service - * @description: ${comments} - * @author: ${author} - * @date: Created in ${datetime} - * @copyright: Copyright (c) ${year} - * @version: V1.0 - * @modified: ${author} - */ -public interface ${className}Service extends IService<${className}> { - -} diff --git a/spring-boot-demo-codegen/src/main/resources/template/ServiceImpl.java.vm b/spring-boot-demo-codegen/src/main/resources/template/ServiceImpl.java.vm deleted file mode 100755 index 56290b3..0000000 --- a/spring-boot-demo-codegen/src/main/resources/template/ServiceImpl.java.vm +++ /dev/null @@ -1,26 +0,0 @@ -package ${package}.${moduleName}.service.impl; - -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import ${package}.${moduleName}.entity.${className}; -import ${package}.${moduleName}.mapper.${className}Mapper; -import ${package}.${moduleName}.service.${className}Service; -import org.springframework.stereotype.Service; -import lombok.extern.slf4j.Slf4j; -/** - *

    - * ${comments} - *

    - * - * @package: ${package}.${moduleName}.service.impl - * @description: ${comments} - * @author: ${author} - * @date: Created in ${datetime} - * @copyright: Copyright (c) ${year} - * @version: V1.0 - * @modified: ${author} - */ -@Service -@Slf4j -public class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements ${className}Service { - -} diff --git a/spring-boot-demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java b/spring-boot-demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java deleted file mode 100644 index b249a2f..0000000 --- a/spring-boot-demo-codegen/src/test/java/com/xkcoding/codegen/CodeGenServiceTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.xkcoding.codegen; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.db.Entity; -import com.xkcoding.codegen.common.PageResult; -import com.xkcoding.codegen.entity.GenConfig; -import com.xkcoding.codegen.entity.TableRequest; -import com.xkcoding.codegen.service.CodeGenService; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; - -/** - *

    - * 代码生成service测试 - *

    - * - * @package: com.xkcoding.codegen - * @description: 代码生成service测试 - * @author: yangkai.shen - * @date: Created in 2019-03-22 10:34 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@Slf4j -public class CodeGenServiceTest { - @Autowired - private CodeGenService codeGenService; - - @Test - public void testTablePage() { - TableRequest request = new TableRequest(); - request.setCurrentPage(1); - request.setPageSize(10); - request.setPrepend("jdbc:mysql://"); - request.setUrl("127.0.0.1:3306/spring-boot-demo"); - request.setUsername("root"); - request.setPassword("root"); - request.setTableName("sec_"); - PageResult pageResult = codeGenService.listTables(request); - log.info("【pageResult】= {}", pageResult); - } - - @Test - @SneakyThrows - public void testGeneratorCode() { - GenConfig config = new GenConfig(); - - TableRequest request = new TableRequest(); - request.setPrepend("jdbc:mysql://"); - request.setUrl("127.0.0.1:3306/spring-boot-demo"); - request.setUsername("root"); - request.setPassword("root"); - request.setTableName("shiro_user"); - config.setRequest(request); - - config.setModuleName("shiro"); - config.setAuthor("Yangkai.Shen"); - config.setComments("用户角色信息"); - config.setPackageName("com.xkcoding"); - config.setTablePrefix("shiro_"); - - byte[] zip = codeGenService.generatorCode(config); - OutputStream outputStream = new FileOutputStream(new File("/Users/yangkai.shen/Desktop/" + request.getTableName() + ".zip")); - IoUtil.write(outputStream, true, zip); - } - -} diff --git a/spring-boot-demo-docker/pom.xml b/spring-boot-demo-docker/pom.xml deleted file mode 100644 index cbbf130..0000000 --- a/spring-boot-demo-docker/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-docker - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-docker - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.4.9 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-docker - - - org.springframework.boot - spring-boot-maven-plugin - - - com.spotify - dockerfile-maven-plugin - ${dockerfile-version} - - ${project.build.finalName} - ${project.version} - - target/${project.build.finalName}.jar - - - - - - - - - - - - - - - - - diff --git a/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java b/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java deleted file mode 100644 index 9f707bd..0000000 --- a/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/SpringBootDemoDockerApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.docker; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.docker - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-11-29 14:59 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoDockerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDockerApplication.class, args); - } -} diff --git a/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java b/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java deleted file mode 100644 index 04884fc..0000000 --- a/spring-boot-demo-docker/src/main/java/com/xkcoding/docker/controller/HelloController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.docker.controller; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * Hello Controller - *

    - * - * @package: com.xkcoding.docker.controller - * @description: Hello Controller - * @author: yangkai.shen - * @date: Created in 2018-11-29 14:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping -public class HelloController { - @GetMapping - public String hello() { - return "Hello,From Docker!"; - } -} diff --git a/spring-boot-demo-dubbo/pom.xml b/spring-boot-demo-dubbo/pom.xml deleted file mode 100644 index 3b48023..0000000 --- a/spring-boot-demo-dubbo/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-dubbo - 1.0.0-SNAPSHOT - - spring-boot-demo-dubbo-common - spring-boot-demo-dubbo-provider - spring-boot-demo-dubbo-consumer - - pom - - spring-boot-demo-dubbo - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.0.0 - 0.10 - - - diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/README.md b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/README.md deleted file mode 100644 index a9bd5ce..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# spring-boot-demo-dubbo-common - -> 此 module 主要是用于公共部分,主要存放工具类,实体,以及服务提供方/调用方的接口定义 - -## pom.xml - -```xml - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-common - - - UTF-8 - UTF-8 - 1.8 - - - - spring-boot-demo-dubbo-common - - - -``` - -## HelloService.java - -```java -/** - *

    - * Hello服务接口 - *

    - * - * @package: com.xkcoding.dubbo.common.service - * @description: Hello服务接口 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:56 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface HelloService { - /** - * 问好 - * - * @param name 姓名 - * @return 问好 - */ - String sayHello(String name); -} -``` - diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/pom.xml b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/pom.xml deleted file mode 100644 index ae7272f..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/pom.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-common - - - UTF-8 - UTF-8 - 1.8 - - - - spring-boot-demo-dubbo-common - - - \ No newline at end of file diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java deleted file mode 100644 index 9704443..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-common/src/main/java/com/xkcoding/dubbo/common/service/HelloService.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.dubbo.common.service; - -/** - *

    - * Hello服务接口 - *

    - * - * @package: com.xkcoding.dubbo.common.service - * @description: Hello服务接口 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:56 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface HelloService { - /** - * 问好 - * - * @param name 姓名 - * @return 问好 - */ - String sayHello(String name); -} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/README.md b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/README.md deleted file mode 100644 index 9754771..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/README.md +++ /dev/null @@ -1,140 +0,0 @@ -# spring-boot-demo-dubbo-consumer - -> 此 module 主要是服务调用方的示例 - -## pom.xml - -```xml - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-consumer - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - com.alibaba.spring.boot - dubbo-spring-boot-starter - ${dubbo.starter.version} - - - - ${project.groupId} - spring-boot-demo-dubbo-common - ${project.version} - - - - com.101tec - zkclient - ${zkclient.version} - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dubbo-consumer - - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo - -spring: - dubbo: - application: - name: spring-boot-demo-dubbo-consumer - registry: zookeeper://127.0.0.1:2181 -``` - -## SpringBootDemoDubboConsumerApplication.java - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.dubbo.consumer - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableDubboConfiguration -public class SpringBootDemoDubboConsumerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDubboConsumerApplication.class, args); - } -} -``` - -## HelloController.java - -```java -/** - *

    - * Hello服务API - *

    - * - * @package: com.xkcoding.dubbo.consumer.controller - * @description: Hello服务API - * @author: yangkai.shen - * @date: Created in 2018-12-25 17:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@Slf4j -public class HelloController { - @Reference - private HelloService helloService; - - @GetMapping("/sayHello") - public String sayHello(@RequestParam(defaultValue = "xkcoding") String name) { - log.info("i'm ready to call someone......"); - return helloService.sayHello(name); - } -} -``` \ No newline at end of file diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/pom.xml b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/pom.xml deleted file mode 100644 index 205b0c6..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-consumer - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - com.alibaba.spring.boot - dubbo-spring-boot-starter - ${dubbo.starter.version} - - - - ${project.groupId} - spring-boot-demo-dubbo-common - ${project.version} - - - - com.101tec - zkclient - ${zkclient.version} - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dubbo-consumer - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java deleted file mode 100644 index ae16000..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/SpringBootDemoDubboConsumerApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.dubbo.consumer; - -import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.dubbo.consumer - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableDubboConfiguration -public class SpringBootDemoDubboConsumerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDubboConsumerApplication.class, args); - } -} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java deleted file mode 100644 index 67bcd9e..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-consumer/src/main/java/com/xkcoding/dubbo/consumer/controller/HelloController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.dubbo.consumer.controller; - -import com.alibaba.dubbo.config.annotation.Reference; -import com.xkcoding.dubbo.common.service.HelloService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * Hello服务API - *

    - * - * @package: com.xkcoding.dubbo.consumer.controller - * @description: Hello服务API - * @author: yangkai.shen - * @date: Created in 2018-12-25 17:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@Slf4j -public class HelloController { - @Reference - private HelloService helloService; - - @GetMapping("/sayHello") - public String sayHello(@RequestParam(defaultValue = "xkcoding") String name) { - log.info("i'm ready to call someone......"); - return helloService.sayHello(name); - } -} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/README.md b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/README.md deleted file mode 100644 index bcbb11e..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# spring-boot-demo-dubbo-provider - -> 此 module 主要是服务提供方示例 - -## pom.xml - -```xml - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-provider - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - com.alibaba.spring.boot - dubbo-spring-boot-starter - ${dubbo.starter.version} - - - - ${project.groupId} - spring-boot-demo-dubbo-common - ${project.version} - - - - com.101tec - zkclient - ${zkclient.version} - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dubbo-provider - - - -``` - -## application.yml - -```yaml -server: - port: 9090 - servlet: - context-path: /demo - -spring: - dubbo: - application: - name: spring-boot-demo-dubbo-provider - registry: zookeeper://localhost:2181 -``` - -## SpringBootDemoDubboProviderApplication.java - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.dubbo.provider - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableDubboConfiguration -@SpringBootApplication -public class SpringBootDemoDubboProviderApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDubboProviderApplication.class, args); - } -} -``` - -## HelloServiceImpl.java - -```java -/** - *

    - * Hello服务实现 - *

    - * - * @package: com.xkcoding.dubbo.provider.service - * @description: Hello服务实现 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Component -@Slf4j -public class HelloServiceImpl implements HelloService { - /** - * 问好 - * - * @param name 姓名 - * @return 问好 - */ - @Override - public String sayHello(String name) { - log.info("someone is calling me......"); - return "say hello to: " + name; - } -} -``` - diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/pom.xml b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/pom.xml deleted file mode 100644 index 5be2bcc..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - spring-boot-demo-dubbo - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-dubbo-provider - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - com.alibaba.spring.boot - dubbo-spring-boot-starter - ${dubbo.starter.version} - - - - ${project.groupId} - spring-boot-demo-dubbo-common - ${project.version} - - - - com.101tec - zkclient - ${zkclient.version} - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dubbo-provider - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java deleted file mode 100644 index 4a407d2..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/SpringBootDemoDubboProviderApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.dubbo.provider; - -import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.dubbo.provider - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableDubboConfiguration -@SpringBootApplication -public class SpringBootDemoDubboProviderApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDubboProviderApplication.class, args); - } -} diff --git a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java b/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java deleted file mode 100644 index 7824e71..0000000 --- a/spring-boot-demo-dubbo/spring-boot-demo-dubbo-provider/src/main/java/com/xkcoding/dubbo/provider/service/HelloServiceImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.dubbo.provider.service; - -import com.alibaba.dubbo.config.annotation.Service; -import com.xkcoding.dubbo.common.service.HelloService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - *

    - * Hello服务实现 - *

    - * - * @package: com.xkcoding.dubbo.provider.service - * @description: Hello服务实现 - * @author: yangkai.shen - * @date: Created in 2018-12-25 16:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Component -@Slf4j -public class HelloServiceImpl implements HelloService { - /** - * 问好 - * - * @param name 姓名 - * @return 问好 - */ - @Override - public String sayHello(String name) { - log.info("someone is calling me......"); - return "say hello to: " + name; - } -} diff --git a/spring-boot-demo-dynamic-datasource/README.md b/spring-boot-demo-dynamic-datasource/README.md deleted file mode 100644 index f6f1c25..0000000 --- a/spring-boot-demo-dynamic-datasource/README.md +++ /dev/null @@ -1,669 +0,0 @@ -# spring-boot-demo-dynamic-datasource - -> 此 demo 主要演示了 Spring Boot 项目如何通过接口`动态添加/删除`数据源,添加数据源之后如何`动态切换`数据源,然后使用 mybatis 查询切换后的数据源的数据。 - -## 1. 环境准备 - -1. 执行 db 目录下的SQL脚本 -2. 在默认数据源下执行 `init.sql` -3. 在所有数据源分别执行 `user.sql` - -## 2. 主要代码 - -### 2.1.pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-dynamic-datasource - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-dynamic-datasource - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - tk.mybatis - mapper-spring-boot-starter - 2.1.5 - - - - mysql - mysql-connector-java - runtime - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dynamic-datasource - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 2.2. 基础配置类 - -- DatasourceConfiguration.java - -> 这个类主要是通过 `DataSourceBuilder` 去构建一个我们自定义的数据源,将其放入 Spring 容器里 - -```java -/** - *

    - * 数据源配置 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 10:27 - */ -@Configuration -public class DatasourceConfiguration { - - @Bean - @ConfigurationProperties(prefix = "spring.datasource") - public DataSource dataSource() { - DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); - dataSourceBuilder.type(DynamicDataSource.class); - return dataSourceBuilder.build(); - } -} -``` - -- MybatisConfiguration.java - -> 这个类主要是将我们上一步构建出来的数据源配置到 Mybatis 的 `SqlSessionFactory` 里 - -```java -/** - *

    - * mybatis配置 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:20 - */ -@Configuration -@MapperScan(basePackages = "com.xkcoding.dynamicdatasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory") -public class MybatisConfiguration { - /** - * 创建会话工厂。 - * - * @param dataSource 数据源 - * @return 会话工厂 - */ - @Bean(name = "sqlSessionFactory") - @SneakyThrows - public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { - SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); - bean.setDataSource(dataSource); - return bean.getObject(); - } -} -``` - -### 2.3. 动态数据源主要逻辑 - -- DatasourceConfigContextHolder.java - -> 该类主要用于绑定当前线程所使用的数据源 id,通过 ThreadLocal 保证同一线程内不可被修改 - -```java -/** - *

    - * 数据源标识管理 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:16 - */ -public class DatasourceConfigContextHolder { - private static final ThreadLocal DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID); - - /** - * 设置默认数据源 - */ - public static void setDefaultDatasource() { - DATASOURCE_HOLDER.remove(); - setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID); - } - - /** - * 获取当前数据源配置id - * - * @return 数据源配置id - */ - public static Long getCurrentDatasourceConfig() { - return DATASOURCE_HOLDER.get(); - } - - /** - * 设置当前数据源配置id - * - * @param id 数据源配置id - */ - public static void setCurrentDatasourceConfig(Long id) { - DATASOURCE_HOLDER.set(id); - } - -} -``` - -- DynamicDataSource.java - -> 该类继承 `com.zaxxer.hikari.HikariDataSource`,主要用于动态切换数据源连接。 - -```java -/** - *

    - * 动态数据源 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 10:41 - */ -@Slf4j -public class DynamicDataSource extends HikariDataSource { - @Override - public Connection getConnection() throws SQLException { - // 获取当前数据源 id - Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig(); - // 根据当前id获取数据源 - HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id); - - if (null == datasource) { - datasource = initDatasource(id); - } - - return datasource.getConnection(); - } - - /** - * 初始化数据源 - * @param id 数据源id - * @return 数据源 - */ - private HikariDataSource initDatasource(Long id) { - HikariDataSource dataSource = new HikariDataSource(); - - // 判断是否是默认数据源 - if (DatasourceHolder.DEFAULT_ID.equals(id)) { - // 默认数据源根据 application.yml 配置的生成 - DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class); - dataSource.setJdbcUrl(properties.getUrl()); - dataSource.setUsername(properties.getUsername()); - dataSource.setPassword(properties.getPassword()); - dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); - } else { - // 不是默认数据源,通过缓存获取对应id的数据源的配置 - DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id); - - if (datasourceConfig == null) { - throw new RuntimeException("无此数据源"); - } - - dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl()); - dataSource.setUsername(datasourceConfig.getUsername()); - dataSource.setPassword(datasourceConfig.getPassword()); - dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); - } - // 将创建的数据源添加到数据源管理器中,绑定当前线程 - DatasourceHolder.INSTANCE.addDatasource(id, dataSource); - return dataSource; - } -} -``` - -- DatasourceScheduler.java - -> 该类主要用于调度任务 - -```java -/** - *

    - * 数据源缓存释放调度器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:42 - */ -public enum DatasourceScheduler { - /** - * 当前实例 - */ - INSTANCE; - - private AtomicInteger cacheTaskNumber = new AtomicInteger(1); - private ScheduledExecutorService scheduler; - - DatasourceScheduler() { - create(); - } - - private void create() { - this.shutdown(); - this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement()))); - } - - private void shutdown() { - if (null != this.scheduler) { - this.scheduler.shutdown(); - } - } - - public void schedule(Runnable task,long delay){ - this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); - } - -} -``` - -- DatasourceManager.java - -> 该类主要用于管理数据源,记录数据源最后使用时间,同时判断是否长时间未使用,超过一定时间未使用,会被释放连接 - -```java -/** - *

    - * 数据源管理类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:27 - */ -public class DatasourceManager { - /** - * 默认释放时间 - */ - private static final Long DEFAULT_RELEASE = 10L; - - /** - * 数据源 - */ - @Getter - private HikariDataSource dataSource; - - /** - * 上一次使用时间 - */ - private LocalDateTime lastUseTime; - - public DatasourceManager(HikariDataSource dataSource) { - this.dataSource = dataSource; - this.lastUseTime = LocalDateTime.now(); - } - - /** - * 是否已过期,如果过期则关闭数据源 - * - * @return 是否过期,{@code true} 过期,{@code false} 未过期 - */ - public boolean isExpired() { - if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) { - return false; - } - this.dataSource.close(); - return true; - } - - /** - * 刷新上次使用时间 - */ - public void refreshTime() { - this.lastUseTime = LocalDateTime.now(); - } -} -``` - -- DatasourceHolder.java - -> 该类主要用于管理数据源,同时通过 `DatasourceScheduler` 定时检查数据源是否长时间未使用,超时则释放连接 - -```java -/** - *

    - * 数据源管理 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:23 - */ -public enum DatasourceHolder { - /** - * 当前实例 - */ - INSTANCE; - - /** - * 启动执行,定时5分钟清理一次 - */ - DatasourceHolder() { - DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000); - } - - /** - * 默认数据源的id - */ - public static final Long DEFAULT_ID = -1L; - - /** - * 管理动态数据源列表。 - */ - private static final Map DATASOURCE_CACHE = new ConcurrentHashMap<>(); - - /** - * 添加动态数据源 - * - * @param id 数据源id - * @param dataSource 数据源 - */ - public synchronized void addDatasource(Long id, HikariDataSource dataSource) { - DatasourceManager datasourceManager = new DatasourceManager(dataSource); - DATASOURCE_CACHE.put(id, datasourceManager); - } - - /** - * 查询动态数据源 - * - * @param id 数据源id - * @return 数据源 - */ - public synchronized HikariDataSource getDatasource(Long id) { - if (DATASOURCE_CACHE.containsKey(id)) { - DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id); - datasourceManager.refreshTime(); - return datasourceManager.getDataSource(); - } - return null; - } - - /** - * 清除超时的数据源 - */ - public synchronized void clearExpiredDatasource() { - DATASOURCE_CACHE.forEach((k, v) -> { - // 排除默认数据源 - if (!DEFAULT_ID.equals(k)) { - if (v.isExpired()) { - DATASOURCE_CACHE.remove(k); - } - } - }); - } - - /** - * 清除动态数据源 - * @param id 数据源id - */ - public synchronized void removeDatasource(Long id) { - if (DATASOURCE_CACHE.containsKey(id)) { - // 关闭数据源 - DATASOURCE_CACHE.get(id).getDataSource().close(); - // 移除缓存 - DATASOURCE_CACHE.remove(id); - } - } -} -``` - -- DatasourceConfigCache.java - -> 该类主要用于缓存数据源的配置,用户生成数据源时,获取数据源连接参数 - -```java -/** - *

    - * 数据源配置缓存 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:13 - */ -public enum DatasourceConfigCache { - /** - * 当前实例 - */ - INSTANCE; - - /** - * 管理动态数据源列表。 - */ - private static final Map CONFIG_CACHE = new ConcurrentHashMap<>(); - - /** - * 添加数据源配置 - * - * @param id 数据源配置id - * @param config 数据源配置 - */ - public synchronized void addConfig(Long id, DatasourceConfig config) { - CONFIG_CACHE.put(id, config); - } - - /** - * 查询数据源配置 - * - * @param id 数据源配置id - * @return 数据源配置 - */ - public synchronized DatasourceConfig getConfig(Long id) { - if (CONFIG_CACHE.containsKey(id)) { - return CONFIG_CACHE.get(id); - } - return null; - } - - /** - * 清除数据源配置 - */ - public synchronized void removeConfig(Long id) { - CONFIG_CACHE.remove(id); - // 同步清除 DatasourceHolder 对应的数据源 - DatasourceHolder.INSTANCE.removeDatasource(id); - } -} -``` - -### 2.4. 启动类 - -> 启动后,使用默认数据源查询数据源配置列表,将其缓存到 `DatasourceConfigCache` 里,以供后续使用 - -```java -/** - *

    - * 启动器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:57 - */ -@SpringBootApplication -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class SpringBootDemoDynamicDatasourceApplication implements CommandLineRunner { - private final DatasourceConfigMapper configMapper; - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDynamicDatasourceApplication.class, args); - } - - @Override - public void run(String... args) { - // 设置默认的数据源 - DatasourceConfigContextHolder.setDefaultDatasource(); - // 查询所有数据库配置列表 - List datasourceConfigs = configMapper.selectAll(); - System.out.println("加载其余数据源配置列表: " + datasourceConfigs); - // 将数据库配置加入缓存 - datasourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config)); - } -} -``` - -### 2.5. 其余代码参考 demo - -## 3. 测试 - -启动项目,可以看到控制台读取到数据库已配置的数据源信息 - -![image-20190905164824155](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062351.png) - -通过 PostMan 等工具测试 - -- 默认数据源查询 - -![image-20190905165240373](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062353.png) - -- 根据数据源id为1的数据源查询 - -![image-20190905165323097](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062354.png) - -- 根据数据源id为2的数据源查询 - -![image-20190905165350355](http://static.xkcoding.com/spring-boot-demo/dynamic-datasource/062355.png) - -- 可以通过测试数据源的`增加/删除`,再去查询对应数据源的数据 - -> 删除数据源: -> -> - DELETE http://localhost:8080/config/{id} -> -> 新增数据源: -> -> - POST http://localhost:8080/config -> -> - 参数: -> -> ```json -> { -> "host": "数据库IP", -> "port": 3306, -> "username": "用户名", -> "password": "密码", -> "database": "数据库" -> } -> ``` - -## 4. 优化 - -如上测试,我们只需要通过在 header 里传递数据源的参数,即可做到动态切换数据源,怎么做到的呢? - -答案就是 `AOP` - -```java -/** - *

    - * 数据源选择器切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:52 - */ -@Aspect -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class DatasourceSelectorAspect { - @Pointcut("execution(public * com.xkcoding.dynamic.datasource.controller.*.*(..))") - public void datasourcePointcut() { - } - - /** - * 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源 - */ - @Before("datasourcePointcut()") - public void doBefore(JoinPoint joinPoint) { - Signature signature = joinPoint.getSignature(); - MethodSignature methodSignature = (MethodSignature) signature; - Method method = methodSignature.getMethod(); - - // 排除不可切换数据源的方法 - DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class); - if (null != annotation) { - DatasourceConfigContextHolder.setDefaultDatasource(); - } else { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; - HttpServletRequest request = attributes.getRequest(); - String configIdInHeader = request.getHeader("Datasource-Config-Id"); - if (StringUtils.hasText(configIdInHeader)) { - long configId = Long.parseLong(configIdInHeader); - DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId); - } else { - DatasourceConfigContextHolder.setDefaultDatasource(); - } - } - } - - /** - * 后置操作,设置回默认的数据源id - */ - @AfterReturning("datasourcePointcut()") - public void doAfter() { - DatasourceConfigContextHolder.setDefaultDatasource(); - } - -} -``` - -此时需要考虑,我们是否每个方法都允许用户去切换数据源呢?答案肯定是不行的,所以我们定义了一个注解去标识,当前方法仅可以使用默认数据源。 - -```java -/** - *

    - * 用户标识仅可以使用默认数据源 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:37 - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface DefaultDatasource { -} -``` - -完结,撒花✿✿ヽ(°▽°)ノ✿ \ No newline at end of file diff --git a/spring-boot-demo-dynamic-datasource/pom.xml b/spring-boot-demo-dynamic-datasource/pom.xml deleted file mode 100644 index 1345a4a..0000000 --- a/spring-boot-demo-dynamic-datasource/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-dynamic-datasource - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-dynamic-datasource - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - tk.mybatis - mapper-spring-boot-starter - 2.1.5 - - - - mysql - mysql-connector-java - runtime - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-dynamic-datasource - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java deleted file mode 100644 index 7a84a96..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.xkcoding.dynamic.datasource; - -import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache; -import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder; -import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper; -import com.xkcoding.dynamic.datasource.model.DatasourceConfig; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import java.util.List; - -/** - *

    - * 启动器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:57 - */ -@SpringBootApplication -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class SpringBootDemoDynamicDatasourceApplication implements CommandLineRunner { - private final DatasourceConfigMapper configMapper; - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoDynamicDatasourceApplication.class, args); - } - - @Override - public void run(String... args) { - // 设置默认的数据源 - DatasourceConfigContextHolder.setDefaultDatasource(); - // 查询所有数据库配置列表 - List datasourceConfigs = configMapper.selectAll(); - System.out.println("加载其余数据源配置列表: " + datasourceConfigs); - // 将数据库配置加入缓存 - datasourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config)); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java deleted file mode 100644 index 533b20a..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.xkcoding.dynamic.datasource.annotation; - -import java.lang.annotation.*; - -/** - *

    - * 用户标识仅可以使用默认数据源 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:37 - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface DefaultDatasource { -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java deleted file mode 100644 index 13da691..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.xkcoding.dynamic.datasource.aspect; - -import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource; -import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder; -import lombok.RequiredArgsConstructor; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.Signature; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; - -/** - *

    - * 数据源选择器切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:52 - */ -@Aspect -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class DatasourceSelectorAspect { - @Pointcut("execution(public * com.xkcoding.dynamic.datasource.controller.*.*(..))") - public void datasourcePointcut() { - } - - /** - * 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源 - */ - @Before("datasourcePointcut()") - public void doBefore(JoinPoint joinPoint) { - Signature signature = joinPoint.getSignature(); - MethodSignature methodSignature = (MethodSignature) signature; - Method method = methodSignature.getMethod(); - - // 排除不可切换数据源的方法 - DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class); - if (null != annotation) { - DatasourceConfigContextHolder.setDefaultDatasource(); - } else { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; - HttpServletRequest request = attributes.getRequest(); - String configIdInHeader = request.getHeader("Datasource-Config-Id"); - if (StringUtils.hasText(configIdInHeader)) { - long configId = Long.parseLong(configIdInHeader); - DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId); - } else { - DatasourceConfigContextHolder.setDefaultDatasource(); - } - } - } - - /** - * 后置操作,设置回默认的数据源id - */ - @AfterReturning("datasourcePointcut()") - public void doAfter() { - DatasourceConfigContextHolder.setDefaultDatasource(); - } - -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java deleted file mode 100644 index 150ac64..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.dynamic.datasource.config; - -import com.xkcoding.dynamic.datasource.datasource.DynamicDataSource; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.sql.DataSource; - -/** - *

    - * 数据源配置 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 10:27 - */ -@Configuration -public class DatasourceConfiguration { - - @Bean - @ConfigurationProperties(prefix = "spring.datasource") - public DataSource dataSource() { - DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); - dataSourceBuilder.type(DynamicDataSource.class); - return dataSourceBuilder.build(); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java deleted file mode 100644 index 2410581..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.xkcoding.dynamic.datasource.config; - -import tk.mybatis.mapper.annotation.RegisterMapper; -import tk.mybatis.mapper.common.Mapper; -import tk.mybatis.mapper.common.MySqlMapper; - -/** - *

    - * 通用 mapper 自定义 mapper 文件 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:23 - */ -@RegisterMapper -public interface MyMapper extends Mapper, MySqlMapper { -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java deleted file mode 100644 index 4747ac7..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.dynamic.datasource.config; - -import lombok.SneakyThrows; -import org.apache.ibatis.session.SqlSessionFactory; -import org.mybatis.spring.SqlSessionFactoryBean; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import tk.mybatis.spring.annotation.MapperScan; - -import javax.sql.DataSource; - -/** - *

    - * mybatis配置 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:20 - */ -@Configuration -@MapperScan(basePackages = "com.xkcoding.dynamic.datasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory") -public class MybatisConfiguration { - /** - * 创建会话工厂。 - * - * @param dataSource 数据源 - * @return 会话工厂 - */ - @Bean(name = "sqlSessionFactory") - @SneakyThrows - public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { - SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); - bean.setDataSource(dataSource); - return bean.getObject(); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java deleted file mode 100644 index 83bae72..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.dynamic.datasource.controller; - -import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource; -import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache; -import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper; -import com.xkcoding.dynamic.datasource.model.DatasourceConfig; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -/** - *

    - * 数据源配置 Controller - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:31 - */ -@RestController -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class DatasourceConfigController { - private final DatasourceConfigMapper configMapper; - - /** - * 保存 - */ - @PostMapping("/config") - @DefaultDatasource - public DatasourceConfig insertConfig(@RequestBody DatasourceConfig config) { - configMapper.insertUseGeneratedKeys(config); - DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config); - return config; - } - - /** - * 保存 - */ - @DeleteMapping("/config/{id}") - @DefaultDatasource - public void removeConfig(@PathVariable Long id) { - configMapper.deleteByPrimaryKey(id); - DatasourceConfigCache.INSTANCE.removeConfig(id); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java deleted file mode 100644 index 67ce6a9..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xkcoding.dynamic.datasource.controller; - -import com.xkcoding.dynamic.datasource.mapper.UserMapper; -import com.xkcoding.dynamic.datasource.model.User; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - *

    - * 用户 Controller - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:40 - */ -@RestController -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class UserController { - private final UserMapper userMapper; - - /** - * 获取用户列表 - */ - @GetMapping("/user") - public List getUserList() { - return userMapper.selectAll(); - } - -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java deleted file mode 100644 index 51324c4..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -import com.xkcoding.dynamic.datasource.model.DatasourceConfig; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - *

    - * 数据源配置缓存 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 17:13 - */ -public enum DatasourceConfigCache { - /** - * 当前实例 - */ - INSTANCE; - - /** - * 管理动态数据源列表。 - */ - private static final Map CONFIG_CACHE = new ConcurrentHashMap<>(); - - /** - * 添加数据源配置 - * - * @param id 数据源配置id - * @param config 数据源配置 - */ - public synchronized void addConfig(Long id, DatasourceConfig config) { - CONFIG_CACHE.put(id, config); - } - - /** - * 查询数据源配置 - * - * @param id 数据源配置id - * @return 数据源配置 - */ - public synchronized DatasourceConfig getConfig(Long id) { - if (CONFIG_CACHE.containsKey(id)) { - return CONFIG_CACHE.get(id); - } - return null; - } - - /** - * 清除数据源配置 - */ - public synchronized void removeConfig(Long id) { - CONFIG_CACHE.remove(id); - // 同步清除 DatasourceHolder 对应的数据源 - DatasourceHolder.INSTANCE.removeDatasource(id); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java deleted file mode 100644 index 389441f..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -/** - *

    - * 数据源标识管理 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:16 - */ -public class DatasourceConfigContextHolder { - private static final ThreadLocal DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID); - - /** - * 设置默认数据源 - */ - public static void setDefaultDatasource() { - DATASOURCE_HOLDER.remove(); - setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID); - } - - /** - * 获取当前数据源配置id - * - * @return 数据源配置id - */ - public static Long getCurrentDatasourceConfig() { - return DATASOURCE_HOLDER.get(); - } - - /** - * 设置当前数据源配置id - * - * @param id 数据源配置id - */ - public static void setCurrentDatasourceConfig(Long id) { - DATASOURCE_HOLDER.set(id); - } - -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java deleted file mode 100644 index f1ab5b9..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -import com.zaxxer.hikari.HikariDataSource; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - *

    - * 数据源管理 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:23 - */ -public enum DatasourceHolder { - /** - * 当前实例 - */ - INSTANCE; - - /** - * 启动执行,定时5分钟清理一次 - */ - DatasourceHolder() { - DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000); - } - - /** - * 默认数据源的id - */ - public static final Long DEFAULT_ID = -1L; - - /** - * 管理动态数据源列表。 - */ - private static final Map DATASOURCE_CACHE = new ConcurrentHashMap<>(); - - /** - * 添加动态数据源 - * - * @param id 数据源id - * @param dataSource 数据源 - */ - public synchronized void addDatasource(Long id, HikariDataSource dataSource) { - DatasourceManager datasourceManager = new DatasourceManager(dataSource); - DATASOURCE_CACHE.put(id, datasourceManager); - } - - /** - * 查询动态数据源 - * - * @param id 数据源id - * @return 数据源 - */ - public synchronized HikariDataSource getDatasource(Long id) { - if (DATASOURCE_CACHE.containsKey(id)) { - DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id); - datasourceManager.refreshTime(); - return datasourceManager.getDataSource(); - } - return null; - } - - /** - * 清除超时的数据源 - */ - public synchronized void clearExpiredDatasource() { - DATASOURCE_CACHE.forEach((k, v) -> { - // 排除默认数据源 - if (!DEFAULT_ID.equals(k)) { - if (v.isExpired()) { - DATASOURCE_CACHE.remove(k); - } - } - }); - } - - /** - * 清除动态数据源 - * @param id 数据源id - */ - public synchronized void removeDatasource(Long id) { - if (DATASOURCE_CACHE.containsKey(id)) { - // 关闭数据源 - DATASOURCE_CACHE.get(id).getDataSource().close(); - // 移除缓存 - DATASOURCE_CACHE.remove(id); - } - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java deleted file mode 100644 index ea57f41..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -import com.zaxxer.hikari.HikariDataSource; -import lombok.Getter; - -import java.time.LocalDateTime; - -/** - *

    - * 数据源管理类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:27 - */ -public class DatasourceManager { - /** - * 默认释放时间 - */ - private static final Long DEFAULT_RELEASE = 10L; - - /** - * 数据源 - */ - @Getter - private HikariDataSource dataSource; - - /** - * 上一次使用时间 - */ - private LocalDateTime lastUseTime; - - public DatasourceManager(HikariDataSource dataSource) { - this.dataSource = dataSource; - this.lastUseTime = LocalDateTime.now(); - } - - /** - * 是否已过期,如果过期则关闭数据源 - * - * @return 是否过期,{@code true} 过期,{@code false} 未过期 - */ - public boolean isExpired() { - if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) { - return false; - } - this.dataSource.close(); - return true; - } - - /** - * 刷新上次使用时间 - */ - public void refreshTime() { - this.lastUseTime = LocalDateTime.now(); - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java deleted file mode 100644 index f8a4939..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - *

    - * 数据源缓存释放调度器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 14:42 - */ -public enum DatasourceScheduler { - /** - * 当前实例 - */ - INSTANCE; - - private AtomicInteger cacheTaskNumber = new AtomicInteger(1); - private ScheduledExecutorService scheduler; - - DatasourceScheduler() { - create(); - } - - private void create() { - this.shutdown(); - this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement()))); - } - - private void shutdown() { - if (null != this.scheduler) { - this.scheduler.shutdown(); - } - } - - public void schedule(Runnable task,long delay){ - this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); - } - -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java deleted file mode 100644 index c32bfdc..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.xkcoding.dynamic.datasource.datasource; - -import com.xkcoding.dynamic.datasource.model.DatasourceConfig; -import com.xkcoding.dynamic.datasource.utils.SpringUtil; -import com.zaxxer.hikari.HikariDataSource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; - -import java.sql.Connection; -import java.sql.SQLException; - -/** - *

    - * 动态数据源 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 10:41 - */ -@Slf4j -public class DynamicDataSource extends HikariDataSource { - @Override - public Connection getConnection() throws SQLException { - // 获取当前数据源 id - Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig(); - // 根据当前id获取数据源 - HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id); - - if (null == datasource) { - datasource = initDatasource(id); - } - - return datasource.getConnection(); - } - - /** - * 初始化数据源 - * @param id 数据源id - * @return 数据源 - */ - private HikariDataSource initDatasource(Long id) { - HikariDataSource dataSource = new HikariDataSource(); - - // 判断是否是默认数据源 - if (DatasourceHolder.DEFAULT_ID.equals(id)) { - // 默认数据源根据 application.yml 配置的生成 - DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class); - dataSource.setJdbcUrl(properties.getUrl()); - dataSource.setUsername(properties.getUsername()); - dataSource.setPassword(properties.getPassword()); - dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); - } else { - // 不是默认数据源,通过缓存获取对应id的数据源的配置 - DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id); - - if (datasourceConfig == null) { - throw new RuntimeException("无此数据源"); - } - - dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl()); - dataSource.setUsername(datasourceConfig.getUsername()); - dataSource.setPassword(datasourceConfig.getPassword()); - dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); - } - // 将创建的数据源添加到数据源管理器中,绑定当前线程 - DatasourceHolder.INSTANCE.addDatasource(id, dataSource); - return dataSource; - } -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java deleted file mode 100644 index 544e31f..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.xkcoding.dynamic.datasource.mapper; - -import com.xkcoding.dynamic.datasource.config.MyMapper; -import com.xkcoding.dynamic.datasource.model.DatasourceConfig; -import org.apache.ibatis.annotations.Mapper; - -/** - *

    - * 数据源配置 Mapper - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:20 - */ -@Mapper -public interface DatasourceConfigMapper extends MyMapper { -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java deleted file mode 100644 index 02ac04b..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.xkcoding.dynamic.datasource.mapper; - -import com.xkcoding.dynamic.datasource.config.MyMapper; -import com.xkcoding.dynamic.datasource.model.User; -import org.apache.ibatis.annotations.Mapper; - -/** - *

    - * 用户 Mapper - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:49 - */ -@Mapper -public interface UserMapper extends MyMapper { -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java deleted file mode 100644 index 5477a94..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.xkcoding.dynamic.datasource.model; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; -import java.io.Serializable; - -/** - *

    - * 数据源配置表 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 10:58 - */ -@Data -@Table(name = "datasource_config") -public class DatasourceConfig implements Serializable { - /** - * 主键 - */ - @Id - @Column(name = "`id`") - @GeneratedValue(generator = "JDBC") - private Long id; - - /** - * 数据库地址 - */ - @Column(name = "`host`") - private String host; - - /** - * 数据库端口 - */ - @Column(name = "`port`") - private Integer port; - - /** - * 数据库用户名 - */ - @Column(name = "`username`") - private String username; - - /** - * 数据库密码 - */ - @Column(name = "`password`") - private String password; - - /** - * 数据库名称 - */ - @Column(name = "`database`") - private String database; - - /** - * 构造JDBC URL - * - * @return JDBC URL - */ - public String buildJdbcUrl() { - return String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&useSSL=false", this.host, this.port, this.database); - } - -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java deleted file mode 100644 index 0c29077..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.dynamic.datasource.model; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; -import java.io.Serializable; - -/** - *

    - * 用户 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:41 - */ -@Data -@Table(name = "test_user") -public class User implements Serializable { - /** - * 主键 - */ - @Id - @Column(name = "`id`") - @GeneratedValue(generator = "JDBC") - private Long id; - - /** - * 姓名 - */ - @Column(name = "`name`") - private String name; -} diff --git a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java b/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java deleted file mode 100644 index a1fb444..0000000 --- a/spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.xkcoding.dynamic.datasource.utils; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -/** - *

    - * Spring 工具类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/4 16:16 - */ -@Slf4j -@Service -@Lazy(false) -public class SpringUtil implements ApplicationContextAware, DisposableBean { - - private static ApplicationContext applicationContext = null; - - /** - * 取得存储在静态变量中的ApplicationContext. - */ - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - - /** - * 实现ApplicationContextAware接口, 注入Context到静态变量中. - */ - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - SpringUtil.applicationContext = applicationContext; - } - - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - @SuppressWarnings("unchecked") - public static T getBean(String name) { - return (T) applicationContext.getBean(name); - } - - /** - * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. - */ - public static T getBean(Class requiredType) { - return applicationContext.getBean(requiredType); - } - - /** - * 清除SpringContextHolder中的ApplicationContext为Null. - */ - public static void clearHolder() { - if (log.isDebugEnabled()) { - log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); - } - applicationContext = null; - } - - /** - * 发布事件 - * - * @param event 事件 - */ - public static void publishEvent(ApplicationEvent event) { - if (applicationContext == null) { - return; - } - applicationContext.publishEvent(event); - } - - /** - * 实现DisposableBean接口, 在Context关闭时清理静态变量. - */ - @Override - public void destroy() { - SpringUtil.clearHolder(); - } - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/README.md b/spring-boot-demo-elasticsearch-rest-high-level-client/README.md deleted file mode 100644 index 6105afe..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/README.md +++ /dev/null @@ -1,493 +0,0 @@ -# spring-boot-demo-elasticsearch-rest-high-level-client - -> 此 demo 主要演示了 Spring Boot 如何集成 `elasticsearch-rest-high-level-client` 完成对 `ElasticSearch 7.x` 版本的基本 CURD 操作 - -## Elasticsearch 升级 - -先升级到 6.8,索引创建,设置 mapping 等操作加参数:include_type_name=true,然后滚动升级到 7,旧索引可以用 type 访问。具体可以参考: - -https://www.elastic.co/cn/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0 - -https://www.elastic.co/guide/en/elasticsearch/reference/7.0/removal-of-types.html - -## 注意 - -作者编写本 demo 时,ElasticSearch 版本为 `7.3.0`,使用 docker 运行,下面是所有步骤: - -1.下载镜像:`docker pull elasticsearch:7.3.0` - -2.下载安装 `docker-compose`,参考文档: https://docs.docker.com/compose/install/ - -```bash -sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose -``` - -3.编写docker-compose 文件 - -```yaml -version: "3" - -services: - es7: - hostname: es7 - container_name: es7 - image: elasticsearch:7.3.0 - volumes: - - "/data/es7/logs:/usr/share/es7/logs:rw" - - "/data/es7/data:/usr/share/es7/data:rw" - restart: on-failure - ports: - - "9200:9200" - - "9300:9300" - environment: - cluster.name: elasticsearch - discovery.type: single-node - logging: - driver: "json-file" - options: - max-size: "50m" - -``` -4.启动: `docker-compose -f elasticsearch.yaml up -d` - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo - com.xkcoding - 1.0.0-SNAPSHOT - - - spring-boot-demo-elasticsearch-rest-high-level-client - spring-boot-demo-elasticsearch-rest-high-level-client - Demo project for Spring Boot - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.hibernate.validator - hibernate-validator - compile - - - - - org.springframework.boot - spring-boot-configuration-processor - - - - - cn.hutool - hutool-all - - - - - org.elasticsearch - elasticsearch - 7.3.0 - - - - - org.elasticsearch.client - elasticsearch-rest-client - 7.3.0 - - - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - 7.3.0 - - - org.elasticsearch.client - elasticsearch-rest-client - - - org.elasticsearch - elasticsearch - - - - - - - org.projectlombok - lombok - true - - - - - - spring-boot-demo-elasticsearch-rest-high-level-client - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## Person.java - -> 实体类 -> - -```java -package com.xkcoding.elasticsearch.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.Date; - -/** - * Person - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:04 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Person implements Serializable { - - private static final long serialVersionUID = 8510634155374943623L; - - /** - * 主键 - */ - private Long id; - - /** - * 名字 - */ - private String name; - - /** - * 国家 - */ - private String country; - - /** - * 年龄 - */ - private Integer age; - - /** - * 生日 - */ - private Date birthday; - - /** - * 介绍 - */ - private String remark; - -} -``` - -## PersonService.java - -```java -package com.xkcoding.elasticsearch.service; - -import com.xkcoding.elasticsearch.model.Person; -import org.springframework.lang.Nullable; - -import java.util.List; - -/** - * PersonService - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:07 - */ -public interface PersonService { - - /** - * create Index - * - * @author fxbin - * @param index elasticsearch index name - */ - void createIndex(String index); - - /** - * delete Index - * - * @author fxbin - * @param index elasticsearch index name - */ - void deleteIndex(String index); - - /** - * insert document source - * - * @author fxbin - * @param index elasticsearch index name - * @param list data source - */ - void insert(String index, List list); - - /** - * update document source - * - * @author fxbin - * @param index elasticsearch index name - * @param list data source - */ - void update(String index, List list); - - /** - * delete document source - * - * @author fxbin - * @param person delete data source and allow null object - */ - void delete(String index, @Nullable Person person); - - /** - * search all doc records - * - * @author fxbin - * @param index elasticsearch index name - * @return person list - */ - List searchList(String index); - -} -``` - -## PersonServiceImpl.java - -> service 实现类型,基本CURD操作 - -```java -package com.xkcoding.elasticsearch.service.impl; - -import cn.hutool.core.bean.BeanUtil; -import com.xkcoding.elasticsearch.model.Person; -import com.xkcoding.elasticsearch.service.base.BaseElasticsearchService; -import com.xkcoding.elasticsearch.service.PersonService; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.search.SearchHit; -import org.springframework.stereotype.Service; -import org.springframework.util.ObjectUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * PersonServiceImpl - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:08 - */ -@Service -public class PersonServiceImpl extends BaseElasticsearchService implements PersonService { - - @Override - public void createIndex(String index) { - createIndexRequest(index); - } - - @Override - public void deleteIndex(String index) { - deleteIndexRequest(index); - } - - @Override - public void insert(String index, List list) { - - try { - list.forEach(person -> { - IndexRequest request = buildIndexRequest(index, String.valueOf(person.getId()), person); - try { - client.index(request, COMMON_OPTIONS); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } finally { - try { - client.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - @Override - public void update(String index, List list) { - list.forEach(person -> { - updateRequest(index, String.valueOf(person.getId()), person); - }); - } - - @Override - public void delete(String index, Person person) { - if (ObjectUtils.isEmpty(person)) { - // 如果person 对象为空,则删除全量 - searchList(index).forEach(p -> { - deleteRequest(index, String.valueOf(p.getId())); - }); - } - deleteRequest(index, String.valueOf(person.getId())); - } - - @Override - public List searchList(String index) { - SearchResponse searchResponse = search(index); - SearchHit[] hits = searchResponse.getHits().getHits(); - List personList = new ArrayList<>(); - Arrays.stream(hits).forEach(hit -> { - Map sourceAsMap = hit.getSourceAsMap(); - Person person = BeanUtil.mapToBean(sourceAsMap, Person.class, true); - personList.add(person); - }); - return personList; - } -} -``` - - -## ElasticsearchApplicationTests.java - -> 主要功能测试,参见service 注释说明 - -```java -package com.xkcoding.elasticsearch; - -import com.xkcoding.elasticsearch.contants.ElasticsearchConstant; -import com.xkcoding.elasticsearch.model.Person; -import com.xkcoding.elasticsearch.service.PersonService; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ElasticsearchApplicationTests { - - @Autowired - private PersonService personService; - - /** - * 测试删除索引 - */ - @Test - public void deleteIndexTest() { - personService.deleteIndex(ElasticsearchConstant.INDEX_NAME); - } - - /** - * 测试创建索引 - */ - @Test - public void createIndexTest() { - personService.createIndex(ElasticsearchConstant.INDEX_NAME); - } - - /** - * 测试新增 - */ - @Test - public void insertTest() { - List list = new ArrayList<>(); - list.add(Person.builder().age(11).birthday(new Date()).country("CN").id(1L).name("哈哈").remark("test1").build()); - list.add(Person.builder().age(22).birthday(new Date()).country("US").id(2L).name("hiahia").remark("test2").build()); - list.add(Person.builder().age(33).birthday(new Date()).country("ID").id(3L).name("呵呵").remark("test3").build()); - - personService.insert(ElasticsearchConstant.INDEX_NAME, list); - } - - /** - * 测试更新 - */ - @Test - public void updateTest() { - Person person = Person.builder().age(33).birthday(new Date()).country("ID_update").id(3L).name("呵呵update").remark("test3_update").build(); - List list = new ArrayList<>(); - list.add(person); - personService.update(ElasticsearchConstant.INDEX_NAME, list); - } - - /** - * 测试删除 - */ - @Test - public void deleteTest() { - personService.delete(ElasticsearchConstant.INDEX_NAME, Person.builder().id(1L).build()); - } - - /** - * 测试查询 - */ - @Test - public void searchListTest() { - List personList = personService.searchList(ElasticsearchConstant.INDEX_NAME); - System.out.println(personList); - } - -} -``` - -## 参考 - -- ElasticSearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html - -- Java High Level REST Client:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.3/java-rest-high.html - diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/pom.xml b/spring-boot-demo-elasticsearch-rest-high-level-client/pom.xml deleted file mode 100644 index 2f85b90..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/pom.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - 4.0.0 - - spring-boot-demo - com.xkcoding - 1.0.0-SNAPSHOT - - - spring-boot-demo-elasticsearch-rest-high-level-client - spring-boot-demo-elasticsearch-rest-high-level-client - Demo project for Spring Boot - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.hibernate.validator - hibernate-validator - compile - - - - - org.springframework.boot - spring-boot-configuration-processor - - - - - cn.hutool - hutool-all - - - - - org.elasticsearch - elasticsearch - 7.3.0 - - - - - org.elasticsearch.client - elasticsearch-rest-client - 7.3.0 - - - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - 7.3.0 - - - org.elasticsearch.client - elasticsearch-rest-client - - - org.elasticsearch - elasticsearch - - - - - - - org.projectlombok - lombok - true - - - - - - spring-boot-demo-elasticsearch-rest-high-level-client - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java deleted file mode 100644 index 2b860f4..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/ElasticsearchApplication.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding.elasticsearch; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * ElasticsearchApplication - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:10 - */ -@SpringBootApplication -public class ElasticsearchApplication { - - public static void main(String[] args) { - SpringApplication.run(ElasticsearchApplication.class, args); - } - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java deleted file mode 100644 index 2ae2af9..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/Result.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.xkcoding.elasticsearch.common; - -import lombok.Data; -import org.springframework.lang.Nullable; - -import java.io.Serializable; - -/** - * Result - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:44 - */ -@Data -public class Result implements Serializable { - - private static final long serialVersionUID = 1696194043024336235L; - - /** - * 错误码 - */ - private int errcode; - - /** - * 错误信息 - */ - private String errmsg; - - /** - * 响应数据 - */ - private T data; - - public Result() { - } - - private Result(ResultCode resultCode) { - this(resultCode.code, resultCode.msg); - } - - private Result(ResultCode resultCode, T data) { - this(resultCode.code, resultCode.msg, data); - } - - private Result(int errcode, String errmsg) { - this(errcode, errmsg, null); - } - - private Result(int errcode, String errmsg, T data) { - this.errcode = errcode; - this.errmsg = errmsg; - this.data = data; - } - - - - /** - * 返回成功 - * - * @param 泛型标记 - * @return 响应信息 {@code Result} - */ - public static Result success() { - return new Result<>(ResultCode.SUCCESS); - } - - - /** - * 返回成功-携带数据 - * - * @param data 响应数据 - * @param 泛型标记 - * @return 响应信息 {@code Result} - */ - public static Result success(@Nullable T data) { - return new Result<>(ResultCode.SUCCESS, data); - } - - - - - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java deleted file mode 100644 index 9f56137..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/common/ResultCode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.elasticsearch.common; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * ResultCode - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:47 - */ -@Getter -@AllArgsConstructor -public enum ResultCode { - - /** - * 接口调用成功 - */ - SUCCESS(0, "Request Successful"), - - /** - * 服务器暂不可用,建议稍候重试。建议重试次数不超过3次。 - */ - FAILURE(-1, "System Busy"); - - final int code; - - final String msg; - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java deleted file mode 100644 index ea1877a..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchAutoConfiguration.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xkcoding.elasticsearch.config; - -import lombok.RequiredArgsConstructor; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * ElasticsearchAutoConfiguration - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 22:59 - */ -@Configuration -@RequiredArgsConstructor(onConstructor_ = @Autowired) -@EnableConfigurationProperties(ElasticsearchProperties.class) -public class ElasticsearchAutoConfiguration { - - private final ElasticsearchProperties elasticsearchProperties; - - private List httpHosts = new ArrayList<>(); - - @Bean - @ConditionalOnMissingBean - public RestHighLevelClient restHighLevelClient() { - - List clusterNodes = elasticsearchProperties.getClusterNodes(); - clusterNodes.forEach(node -> { - try { - String[] parts = StringUtils.split(node, ":"); - Assert.notNull(parts, "Must defined"); - Assert.state(parts.length == 2, "Must be defined as 'host:port'"); - httpHosts.add(new HttpHost(parts[0], Integer.parseInt(parts[1]), elasticsearchProperties.getSchema())); - } catch (Exception e) { - throw new IllegalStateException("Invalid ES nodes " + "property '" + node + "'", e); - } - }); - RestClientBuilder builder = RestClient.builder(httpHosts.toArray(new HttpHost[0])); - - return getRestHighLevelClient(builder, elasticsearchProperties); - } - - - /** - * get restHistLevelClient - * - * @param builder RestClientBuilder - * @param elasticsearchProperties elasticsearch default properties - * @return {@link org.elasticsearch.client.RestHighLevelClient} - * @author fxbin - */ - private static RestHighLevelClient getRestHighLevelClient(RestClientBuilder builder, ElasticsearchProperties elasticsearchProperties) { - - // Callback used the default {@link RequestConfig} being set to the {@link CloseableHttpClient} - builder.setRequestConfigCallback(requestConfigBuilder -> { - requestConfigBuilder.setConnectTimeout(elasticsearchProperties.getConnectTimeout()); - requestConfigBuilder.setSocketTimeout(elasticsearchProperties.getSocketTimeout()); - requestConfigBuilder.setConnectionRequestTimeout(elasticsearchProperties.getConnectionRequestTimeout()); - return requestConfigBuilder; - }); - - // Callback used to customize the {@link CloseableHttpClient} instance used by a {@link RestClient} instance. - builder.setHttpClientConfigCallback(httpClientBuilder -> { - httpClientBuilder.setMaxConnTotal(elasticsearchProperties.getMaxConnectTotal()); - httpClientBuilder.setMaxConnPerRoute(elasticsearchProperties.getMaxConnectPerRoute()); - return httpClientBuilder; - }); - - // Callback used the basic credential auth - ElasticsearchProperties.Account account = elasticsearchProperties.getAccount(); - if (!StringUtils.isEmpty(account.getUsername()) && !StringUtils.isEmpty(account.getUsername())) { - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - - credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(account.getUsername(), account.getPassword())); - } - return new RestHighLevelClient(builder); - } - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java deleted file mode 100644 index ee5db86..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/config/ElasticsearchProperties.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.xkcoding.elasticsearch.config; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import javax.validation.constraints.NotNull; -import java.util.ArrayList; -import java.util.List; - -/** - * ElasticsearchProperties - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 22:58 - */ -@Data -@Builder -@Component -@NoArgsConstructor -@AllArgsConstructor -@ConfigurationProperties(prefix = "demo.data.elasticsearch") -public class ElasticsearchProperties { - - /** - * 请求协议 - */ - private String schema = "http"; - - /** - * 集群名称 - */ - private String clusterName = "elasticsearch"; - - /** - * 集群节点 - */ - @NotNull(message = "集群节点不允许为空") - private List clusterNodes = new ArrayList<>(); - - /** - * 连接超时时间(毫秒) - */ - private Integer connectTimeout = 1000; - - /** - * socket 超时时间 - */ - private Integer socketTimeout = 30000; - - /** - * 连接请求超时时间 - */ - private Integer connectionRequestTimeout = 500; - - /** - * 每个路由的最大连接数量 - */ - private Integer maxConnectPerRoute = 10; - - /** - * 最大连接总数量 - */ - private Integer maxConnectTotal = 30; - - /** - * 索引配置信息 - */ - private Index index = new Index(); - - /** - * 认证账户 - */ - private Account account = new Account(); - - /** - * 索引配置信息 - */ - @Data - public static class Index { - - /** - * 分片数量 - */ - private Integer numberOfShards = 3; - - /** - * 副本数量 - */ - private Integer numberOfReplicas = 2; - - } - - /** - * 认证账户 - */ - @Data - public static class Account { - - /** - * 认证用户 - */ - private String username; - - /** - * 认证密码 - */ - private String password; - - } - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java deleted file mode 100644 index 4a989e3..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/contants/ElasticsearchConstant.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.xkcoding.elasticsearch.contants; - -/** - * ElasticsearchConstant - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:03 - */ -public interface ElasticsearchConstant { - - /** - * 索引名称 - */ - String INDEX_NAME = "person"; - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java deleted file mode 100644 index ca92416..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/exception/ElasticsearchException.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.elasticsearch.exception; - -import com.xkcoding.elasticsearch.common.ResultCode; -import lombok.Getter; - -/** - * ElasticsearchException - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:53 - */ -public class ElasticsearchException extends RuntimeException { - - @Getter - private int errcode; - - @Getter - private String errmsg; - - public ElasticsearchException(ResultCode resultCode) { - this(resultCode.getCode(), resultCode.getMsg()); - } - - public ElasticsearchException(String message) { - super(message); - } - - public ElasticsearchException(Integer errcode, String errmsg) { - super(errmsg); - this.errcode = errcode; - this.errmsg = errmsg; - } - - public ElasticsearchException(String message, Throwable cause) { - super(message, cause); - } - - public ElasticsearchException(Throwable cause) { - super(cause); - } - - public ElasticsearchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java deleted file mode 100644 index d6c59cb..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/model/Person.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.xkcoding.elasticsearch.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.Date; - -/** - * Person - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:04 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Person implements Serializable { - - private static final long serialVersionUID = 8510634155374943623L; - - /** - * 主键 - */ - private Long id; - - /** - * 名字 - */ - private String name; - - /** - * 国家 - */ - private String country; - - /** - * 年龄 - */ - private Integer age; - - /** - * 生日 - */ - private Date birthday; - - /** - * 介绍 - */ - private String remark; - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java deleted file mode 100644 index 0ffba43..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/PersonService.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.elasticsearch.service; - -import com.xkcoding.elasticsearch.model.Person; -import org.springframework.lang.Nullable; - -import java.util.List; - -/** - * PersonService - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:07 - */ -public interface PersonService { - - /** - * create Index - * - * @author fxbin - * @param index elasticsearch index name - */ - void createIndex(String index); - - /** - * delete Index - * - * @author fxbin - * @param index elasticsearch index name - */ - void deleteIndex(String index); - - /** - * insert document source - * - * @author fxbin - * @param index elasticsearch index name - * @param list data source - */ - void insert(String index, List list); - - /** - * update document source - * - * @author fxbin - * @param index elasticsearch index name - * @param list data source - */ - void update(String index, List list); - - /** - * delete document source - * - * @author fxbin - * @param person delete data source and allow null object - */ - void delete(String index, @Nullable Person person); - - /** - * search all doc records - * - * @author fxbin - * @param index elasticsearch index name - * @return person list - */ - List searchList(String index); - -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java deleted file mode 100644 index fa919be..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/base/BaseElasticsearchService.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.xkcoding.elasticsearch.service.base; - -import cn.hutool.core.bean.BeanUtil; -import com.xkcoding.elasticsearch.config.ElasticsearchProperties; -import com.xkcoding.elasticsearch.exception.ElasticsearchException; -import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; -import org.elasticsearch.action.delete.DeleteRequest; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.HttpAsyncResponseConsumerFactory; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.indices.CreateIndexRequest; -import org.elasticsearch.client.indices.CreateIndexResponse; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.search.builder.SearchSourceBuilder; - -import javax.annotation.Resource; -import java.io.IOException; - -/** - * BaseElasticsearchService - * - * @author fxbin - * @version 1.0v - * @since 2019/9/16 15:44 - */ -@Slf4j -public abstract class BaseElasticsearchService { - - @Resource - protected RestHighLevelClient client; - - @Resource - private ElasticsearchProperties elasticsearchProperties; - - protected static final RequestOptions COMMON_OPTIONS; - - static { - RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); - - // 默认缓冲限制为100MB,此处修改为30MB。 - builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024)); - COMMON_OPTIONS = builder.build(); - } - - /** - * create elasticsearch index (asyc) - * - * @param index elasticsearch index - * @author fxbin - */ - protected void createIndexRequest(String index) { - try { - CreateIndexRequest request = new CreateIndexRequest(index); - // Settings for this index - request.settings(Settings.builder().put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards()).put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas())); - - CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS); - - log.info(" whether all of the nodes have acknowledged the request : {}", createIndexResponse.isAcknowledged()); - log.info(" Indicates whether the requisite number of shard copies were started for each shard in the index before timing out :{}", createIndexResponse.isShardsAcknowledged()); - } catch (IOException e) { - throw new ElasticsearchException("创建索引 {" + index + "} 失败"); - } - } - - /** - * delete elasticsearch index - * - * @param index elasticsearch index name - * @author fxbin - */ - protected void deleteIndexRequest(String index) { - DeleteIndexRequest deleteIndexRequest = buildDeleteIndexRequest(index); - try { - client.indices().delete(deleteIndexRequest, COMMON_OPTIONS); - } catch (IOException e) { - throw new ElasticsearchException("删除索引 {" + index + "} 失败"); - } - } - - /** - * build DeleteIndexRequest - * - * @param index elasticsearch index name - * @author fxbin - */ - private static DeleteIndexRequest buildDeleteIndexRequest(String index) { - return new DeleteIndexRequest(index); - } - - /** - * build IndexRequest - * - * @param index elasticsearch index name - * @param id request object id - * @param object request object - * @return {@link org.elasticsearch.action.index.IndexRequest} - * @author fxbin - */ - protected static IndexRequest buildIndexRequest(String index, String id, Object object) { - return new IndexRequest(index).id(id).source(BeanUtil.beanToMap(object), XContentType.JSON); - } - - /** - * exec updateRequest - * - * @param index elasticsearch index name - * @param id Document id - * @param object request object - * @author fxbin - */ - protected void updateRequest(String index, String id, Object object) { - try { - UpdateRequest updateRequest = new UpdateRequest(index, id).doc(BeanUtil.beanToMap(object), XContentType.JSON); - client.update(updateRequest, COMMON_OPTIONS); - } catch (IOException e) { - throw new ElasticsearchException("更新索引 {" + index + "} 数据 {" + object + "} 失败"); - } - } - - /** - * exec deleteRequest - * - * @param index elasticsearch index name - * @param id Document id - * @author fxbin - */ - protected void deleteRequest(String index, String id) { - try { - DeleteRequest deleteRequest = new DeleteRequest(index, id); - client.delete(deleteRequest, COMMON_OPTIONS); - } catch (IOException e) { - throw new ElasticsearchException("删除索引 {" + index + "} 数据id {" + id + "} 失败"); - } - } - - /** - * search all - * - * @param index elasticsearch index name - * @return {@link SearchResponse} - * @author fxbin - */ - protected SearchResponse search(String index) { - SearchRequest searchRequest = new SearchRequest(index); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.matchAllQuery()); - searchRequest.source(searchSourceBuilder); - SearchResponse searchResponse = null; - try { - searchResponse = client.search(searchRequest, COMMON_OPTIONS); - } catch (IOException e) { - e.printStackTrace(); - } - return searchResponse; - } -} diff --git a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java b/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java deleted file mode 100644 index 5e9ceca..0000000 --- a/spring-boot-demo-elasticsearch-rest-high-level-client/src/main/java/com/xkcoding/elasticsearch/service/impl/PersonServiceImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.xkcoding.elasticsearch.service.impl; - -import cn.hutool.core.bean.BeanUtil; -import com.xkcoding.elasticsearch.model.Person; -import com.xkcoding.elasticsearch.service.base.BaseElasticsearchService; -import com.xkcoding.elasticsearch.service.PersonService; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.search.SearchHit; -import org.springframework.stereotype.Service; -import org.springframework.util.ObjectUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * PersonServiceImpl - * - * @author fxbin - * @version v1.0 - * @since 2019/9/15 23:08 - */ -@Service -public class PersonServiceImpl extends BaseElasticsearchService implements PersonService { - - @Override - public void createIndex(String index) { - createIndexRequest(index); - } - - @Override - public void deleteIndex(String index) { - deleteIndexRequest(index); - } - - @Override - public void insert(String index, List list) { - - try { - list.forEach(person -> { - IndexRequest request = buildIndexRequest(index, String.valueOf(person.getId()), person); - try { - client.index(request, COMMON_OPTIONS); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } finally { - try { - client.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - @Override - public void update(String index, List list) { - list.forEach(person -> { - updateRequest(index, String.valueOf(person.getId()), person); - }); - } - - @Override - public void delete(String index, Person person) { - if (ObjectUtils.isEmpty(person)) { - // 如果person 对象为空,则删除全量 - searchList(index).forEach(p -> { - deleteRequest(index, String.valueOf(p.getId())); - }); - } - deleteRequest(index, String.valueOf(person.getId())); - } - - @Override - public List searchList(String index) { - SearchResponse searchResponse = search(index); - SearchHit[] hits = searchResponse.getHits().getHits(); - List personList = new ArrayList<>(); - Arrays.stream(hits).forEach(hit -> { - Map sourceAsMap = hit.getSourceAsMap(); - Person person = BeanUtil.mapToBean(sourceAsMap, Person.class, true); - personList.add(person); - }); - return personList; - } -} diff --git a/spring-boot-demo-elasticsearch/README.md b/spring-boot-demo-elasticsearch/README.md deleted file mode 100644 index ae2cf7f..0000000 --- a/spring-boot-demo-elasticsearch/README.md +++ /dev/null @@ -1,433 +0,0 @@ -# spring-boot-demo-elasticsearch - -> 此 demo 主要演示了 Spring Boot 如何集成 `spring-boot-starter-data-elasticsearch` 完成对 ElasticSearch 的高级使用技巧,包括创建索引、配置映射、删除索引、增删改查基本操作、复杂查询、高级查询、聚合查询等。 - -## 注意 - -作者编写本demo时,ElasticSearch版本为 `6.5.3`,使用 docker 运行,下面是所有步骤: - -1. 下载镜像:`docker pull elasticsearch:6.5.3` - -2. 运行容器:`docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch-6.5.3 elasticsearch:6.5.3` - -3. 进入容器:`docker exec -it elasticsearch-6.5.3 /bin/bash` - -4. 安装 ik 分词器:`./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.3/elasticsearch-analysis-ik-6.5.3.zip` - -5. 修改 es 配置文件:`vi ./config/elasticsearch.yml - - ```yaml - cluster.name: "docker-cluster" - network.host: 0.0.0.0 - - # minimum_master_nodes need to be explicitly set when bound on a public IP - # set to 1 to allow single node clusters - # Details: https://github.com/elastic/elasticsearch/pull/17288 - discovery.zen.minimum_master_nodes: 1 - - # just for elasticsearch-head plugin - http.cors.enabled: true - http.cors.allow-origin: "*" - ``` - -6. 退出容器:`exit` - -7. 停止容器:`docker stop elasticsearch-6.5.3` - -8. 启动容器:`docker start elasticsearch-6.5.3` - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-elasticsearch - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-elasticsearch - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-elasticsearch - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-elasticsearch - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## Person.java - -> 实体类 -> -> @Document 注解主要声明索引名、类型名、分片数量和备份数量 -> -> @Field 注解主要声明字段对应ES的类型 - -```java -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.elasticsearch.model - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018-12-20 17:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Document(indexName = EsConsts.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0) -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Person { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 名字 - */ - @Field(type = FieldType.Keyword) - private String name; - - /** - * 国家 - */ - @Field(type = FieldType.Keyword) - private String country; - - /** - * 年龄 - */ - @Field(type = FieldType.Integer) - private Integer age; - - /** - * 生日 - */ - @Field(type = FieldType.Date) - private Date birthday; - - /** - * 介绍 - */ - @Field(type = FieldType.Text, analyzer = "ik_smart") - private String remark; -} -``` - -## PersonRepository.java - -```java -/** - *

    - * 用户持久层 - *

    - * - * @package: com.xkcoding.elasticsearch.repository - * @description: 用户持久层 - * @author: yangkai.shen - * @date: Created in 2018-12-20 19:00 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface PersonRepository extends ElasticsearchRepository { - - /** - * 根据年龄区间查询 - * - * @param min 最小值 - * @param max 最大值 - * @return 满足条件的用户列表 - */ - List findByAgeBetween(Integer min, Integer max); -} -``` - -## TemplateTest.java - -> 主要测试创建索引、映射配置、删除索引 - -```java -/** - *

    - * 测试 ElasticTemplate 的创建/删除 - *

    - * - * @package: com.xkcoding.elasticsearch.template - * @description: 测试 ElasticTemplate 的创建/删除 - * @author: yangkai.shen - * @date: Created in 2018-12-20 17:46 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests { - @Autowired - private ElasticsearchTemplate esTemplate; - - /** - * 测试 ElasticTemplate 创建 index - */ - @Test - public void testCreateIndex() { - // 创建索引,会根据Item类的@Document注解信息来创建 - esTemplate.createIndex(Person.class); - - // 配置映射,会根据Item类中的id、Field等字段来自动完成映射 - esTemplate.putMapping(Person.class); - } - - /** - * 测试 ElasticTemplate 删除 index - */ - @Test - public void testDeleteIndex() { - esTemplate.deleteIndex(Person.class); - } -} -``` - -## PersonRepositoryTest.java - -> 主要功能,参见方法上方注释 - -```java -/** - *

    - * 测试 Repository 操作ES - *

    - * - * @package: com.xkcoding.elasticsearch.repository - * @description: 测试 Repository 操作ES - * @author: yangkai.shen - * @date: Created in 2018-12-20 19:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class PersonRepositoryTest extends SpringBootDemoElasticsearchApplicationTests { - @Autowired - private PersonRepository repo; - - /** - * 测试新增 - */ - @Test - public void save() { - Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。"); - Person save = repo.save(person); - log.info("【save】= {}", save); - } - - /** - * 测试批量新增 - */ - @Test - public void saveList() { - List personList = Lists.newArrayList(); - personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。")); - personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。")); - personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。")); - Iterable people = repo.saveAll(personList); - log.info("【people】= {}", people); - } - - /** - * 测试更新 - */ - @Test - public void update() { - repo.findById(1L).ifPresent(person -> { - person.setRemark(person.getRemark() + "\n更新更新更新更新更新"); - Person save = repo.save(person); - log.info("【save】= {}", save); - }); - } - - /** - * 测试删除 - */ - @Test - public void delete() { - // 主键删除 - repo.deleteById(1L); - - // 对象删除 - repo.findById(2L).ifPresent(person -> repo.delete(person)); - - // 批量删除 - repo.deleteAll(repo.findAll()); - } - - /** - * 测试普通查询,按生日倒序 - */ - @Test - public void select() { - repo.findAll(Sort.by(Sort.Direction.DESC, "birthday")) - .forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday()))); - } - - /** - * 自定义查询,根据年龄范围查询 - */ - @Test - public void customSelectRangeOfAge() { - repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge())); - } - - /** - * 高级查询 - */ - @Test - public void advanceSelect() { - // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装 - MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "孙权"); - log.info("【queryBuilder】= {}", queryBuilder.toString()); - - repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person)); - } - - /** - * 自定义高级查询 - */ - @Test - public void customAdvanceSelect() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 添加基本的分词条件 - queryBuilder.withQuery(QueryBuilders.matchQuery("remark", "东汉")); - // 排序条件 - queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC)); - // 分页条件 - queryBuilder.withPageable(PageRequest.of(0, 2)); - Page people = repo.search(queryBuilder.build()); - log.info("【people】总条数 = {}", people.getTotalElements()); - log.info("【people】总页数 = {}", people.getTotalPages()); - people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge())); - } - - /** - * 测试聚合,测试平均年龄 - */ - @Test - public void agg() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 不查询任何结果 - queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); - - // 平均年龄 - queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age")); - - log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); - - AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); - double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue(); - log.info("【avgAge】= {}", avgAge); - } - - /** - * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少 - */ - @Test - public void advanceAgg() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 不查询任何结果 - queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); - - // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age - queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country") - // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄 - .subAggregation(AggregationBuilders.avg("avg").field("age"))); - - log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); - - // 3. 查询 - AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); - - // 4. 解析 - // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 - StringTerms country = (StringTerms) people.getAggregation("country"); - // 4.2. 获取桶 - List buckets = country.getBuckets(); - for (StringTerms.Bucket bucket : buckets) { - // 4.3. 获取桶中的key,即国家名称 4.4. 获取桶中的文档数量 - log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount()); - // 4.5. 获取子聚合结果: - InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg"); - log.info("平均年龄:{}", avg); - } - } - -} -``` - -## 参考 - -1. ElasticSearch 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/6.x/getting-started.html -2. spring-data-elasticsearch 官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.2.RELEASE/reference/html/ - diff --git a/spring-boot-demo-elasticsearch/pom.xml b/spring-boot-demo-elasticsearch/pom.xml deleted file mode 100644 index 340d524..0000000 --- a/spring-boot-demo-elasticsearch/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-elasticsearch - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-elasticsearch - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-elasticsearch - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-elasticsearch - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java b/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java deleted file mode 100644 index 12218b7..0000000 --- a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplication.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.elasticsearch; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.elasticsearch - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/27 22:52 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoElasticsearchApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoElasticsearchApplication.class, args); - } - -} diff --git a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java b/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java deleted file mode 100644 index 7753b45..0000000 --- a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/constants/EsConsts.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.elasticsearch.constants; - -/** - *

    - * ES常量池 - *

    - * - * @package: com.xkcoding.elasticsearch.constants - * @description: ES常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-20 17:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface EsConsts { - /** - * 索引名称 - */ - String INDEX_NAME = "person"; - - /** - * 类型名称 - */ - String TYPE_NAME = "person"; -} diff --git a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java b/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java deleted file mode 100644 index 614af61..0000000 --- a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/model/Person.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.xkcoding.elasticsearch.model; - -import com.xkcoding.elasticsearch.constants.EsConsts; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.annotations.Field; -import org.springframework.data.elasticsearch.annotations.FieldType; - -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.elasticsearch.model - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018-12-20 17:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Document(indexName = EsConsts.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0) -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Person { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 名字 - */ - @Field(type = FieldType.Keyword) - private String name; - - /** - * 国家 - */ - @Field(type = FieldType.Keyword) - private String country; - - /** - * 年龄 - */ - @Field(type = FieldType.Integer) - private Integer age; - - /** - * 生日 - */ - @Field(type = FieldType.Date) - private Date birthday; - - /** - * 介绍 - */ - @Field(type = FieldType.Text, analyzer = "ik_smart") - private String remark; -} diff --git a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java b/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java deleted file mode 100644 index 5158d33..0000000 --- a/spring-boot-demo-elasticsearch/src/main/java/com/xkcoding/elasticsearch/repository/PersonRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.elasticsearch.repository; - -import com.xkcoding.elasticsearch.model.Person; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; - -import java.util.List; - -/** - *

    - * 用户持久层 - *

    - * - * @package: com.xkcoding.elasticsearch.repository - * @description: 用户持久层 - * @author: yangkai.shen - * @date: Created in 2018-12-20 19:00 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface PersonRepository extends ElasticsearchRepository { - - /** - * 根据年龄区间查询 - * - * @param min 最小值 - * @param max 最大值 - * @return 满足条件的用户列表 - */ - List findByAgeBetween(Integer min, Integer max); -} diff --git a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java b/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java deleted file mode 100644 index 32b3f7e..0000000 --- a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/SpringBootDemoElasticsearchApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.elasticsearch; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoElasticsearchApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java b/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java deleted file mode 100644 index b62f196..0000000 --- a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/repository/PersonRepositoryTest.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.xkcoding.elasticsearch.repository; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.json.JSONUtil; -import com.google.common.collect.Lists; -import com.xkcoding.elasticsearch.SpringBootDemoElasticsearchApplicationTests; -import com.xkcoding.elasticsearch.model.Person; -import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.index.query.MatchQueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.search.aggregations.AggregationBuilders; -import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; -import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg; -import org.elasticsearch.search.sort.SortBuilders; -import org.elasticsearch.search.sort.SortOrder; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; -import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; - -import java.util.List; - -/** - *

    - * 测试 Repository 操作ES - *

    - * - * @package: com.xkcoding.elasticsearch.repository - * @description: 测试 Repository 操作ES - * @author: yangkai.shen - * @date: Created in 2018-12-20 19:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class PersonRepositoryTest extends SpringBootDemoElasticsearchApplicationTests { - @Autowired - private PersonRepository repo; - - /** - * 测试新增 - */ - @Test - public void save() { - Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。"); - Person save = repo.save(person); - log.info("【save】= {}", save); - } - - /** - * 测试批量新增 - */ - @Test - public void saveList() { - List personList = Lists.newArrayList(); - personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。")); - personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。")); - personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。")); - Iterable people = repo.saveAll(personList); - log.info("【people】= {}", people); - } - - /** - * 测试更新 - */ - @Test - public void update() { - repo.findById(1L).ifPresent(person -> { - person.setRemark(person.getRemark() + "\n更新更新更新更新更新"); - Person save = repo.save(person); - log.info("【save】= {}", save); - }); - } - - /** - * 测试删除 - */ - @Test - public void delete() { - // 主键删除 - repo.deleteById(1L); - - // 对象删除 - repo.findById(2L).ifPresent(person -> repo.delete(person)); - - // 批量删除 - repo.deleteAll(repo.findAll()); - } - - /** - * 测试普通查询,按生日倒序 - */ - @Test - public void select() { - repo.findAll(Sort.by(Sort.Direction.DESC, "birthday")) - .forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday()))); - } - - /** - * 自定义查询,根据年龄范围查询 - */ - @Test - public void customSelectRangeOfAge() { - repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge())); - } - - /** - * 高级查询 - */ - @Test - public void advanceSelect() { - // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装 - MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "孙权"); - log.info("【queryBuilder】= {}", queryBuilder.toString()); - - repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person)); - } - - /** - * 自定义高级查询 - */ - @Test - public void customAdvanceSelect() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 添加基本的分词条件 - queryBuilder.withQuery(QueryBuilders.matchQuery("remark", "东汉")); - // 排序条件 - queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC)); - // 分页条件 - queryBuilder.withPageable(PageRequest.of(0, 2)); - Page people = repo.search(queryBuilder.build()); - log.info("【people】总条数 = {}", people.getTotalElements()); - log.info("【people】总页数 = {}", people.getTotalPages()); - people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge())); - } - - /** - * 测试聚合,测试平均年龄 - */ - @Test - public void agg() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 不查询任何结果 - queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); - - // 平均年龄 - queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age")); - - log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); - - AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); - double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue(); - log.info("【avgAge】= {}", avgAge); - } - - /** - * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少 - */ - @Test - public void advanceAgg() { - // 构造查询条件 - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - // 不查询任何结果 - queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); - - // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age - queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country") - // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄 - .subAggregation(AggregationBuilders.avg("avg").field("age"))); - - log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build())); - - // 3. 查询 - AggregatedPage people = (AggregatedPage) repo.search(queryBuilder.build()); - - // 4. 解析 - // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 - StringTerms country = (StringTerms) people.getAggregation("country"); - // 4.2. 获取桶 - List buckets = country.getBuckets(); - for (StringTerms.Bucket bucket : buckets) { - // 4.3. 获取桶中的key,即国家名称 4.4. 获取桶中的文档数量 - log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount()); - // 4.5. 获取子聚合结果: - InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg"); - log.info("平均年龄:{}", avg); - } - } - -} \ No newline at end of file diff --git a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java b/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java deleted file mode 100644 index 4e95257..0000000 --- a/spring-boot-demo-elasticsearch/src/test/java/com/xkcoding/elasticsearch/template/TemplateTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.elasticsearch.template; - -import com.xkcoding.elasticsearch.SpringBootDemoElasticsearchApplicationTests; -import com.xkcoding.elasticsearch.model.Person; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; - -/** - *

    - * 测试 ElasticTemplate 的创建/删除 - *

    - * - * @package: com.xkcoding.elasticsearch.template - * @description: 测试 ElasticTemplate 的创建/删除 - * @author: yangkai.shen - * @date: Created in 2018-12-20 17:46 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests { - @Autowired - private ElasticsearchTemplate esTemplate; - - /** - * 测试 ElasticTemplate 创建 index - */ - @Test - public void testCreateIndex() { - // 创建索引,会根据Item类的@Document注解信息来创建 - esTemplate.createIndex(Person.class); - - // 配置映射,会根据Item类中的id、Field等字段来自动完成映射 - esTemplate.putMapping(Person.class); - } - - /** - * 测试 ElasticTemplate 删除 index - */ - @Test - public void testDeleteIndex() { - esTemplate.deleteIndex(Person.class); - } -} diff --git a/spring-boot-demo-email/README.md b/spring-boot-demo-email/README.md deleted file mode 100644 index 08e70db..0000000 --- a/spring-boot-demo-email/README.md +++ /dev/null @@ -1,456 +0,0 @@ -# spring-boot-demo-email - -> 此 demo 主要演示了 Spring Boot 如何整合邮件功能,包括发送简单文本邮件、HTML邮件(包括模板HTML邮件)、附件邮件、静态资源邮件。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-email - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-email - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.1 - - - - - - org.springframework.boot - spring-boot-starter-mail - - - - - com.github.ulisesbocchio - jasypt-spring-boot-starter - ${jasypt.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - - spring-boot-demo-email - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -spring: - mail: - host: smtp.mxhichina.com - port: 465 - username: spring-boot-demo@xkcoding.com - # 使用 jasypt 加密密码,使用com.xkcoding.email.PasswordTest.testGeneratePassword 生成加密密码,替换 ENC(加密密码) - password: ENC(OT0qGOpXrr1Iog1W+fjOiIDCJdBjHyhy) - protocol: smtp - test-connection: true - default-encoding: UTF-8 - properties: - mail.smtp.auth: true - mail.smtp.starttls.enable: true - mail.smtp.starttls.required: true - mail.smtp.ssl.enable: true - mail.display.sendmail: spring-boot-demo -# 为 jasypt 配置解密秘钥 -jasypt: - encryptor: - password: spring-boot-demo - -``` - -## MailService.java - -```java -/** - *

    - * 邮件接口 - *

    - * - * @package: com.xkcoding.email.service - * @description: 邮件接口 - * @author: yangkai.shen - * @date: Created in 2018/11/21 11:16 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface MailService { - /** - * 发送文本邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - */ - void sendSimpleMail(String to, String subject, String content, String... cc); - - /** - * 发送HTML邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException; - - /** - * 发送带附件的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param filePath 附件地址 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException; - - /** - * 发送正文中有静态资源的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param rscPath 静态资源地址 - * @param rscId 静态资源id - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException; - -} -``` - -## MailServiceImpl.java - -```java -/** - *

    - * 邮件接口 - *

    - * - * @package: com.xkcoding.email.service.impl - * @description: 邮件接口 - * @author: yangkai.shen - * @date: Created in 2018/11/21 13:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class MailServiceImpl implements MailService { - @Autowired - private JavaMailSender mailSender; - @Value("${spring.mail.username}") - private String from; - - /** - * 发送文本邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - */ - @Override - public void sendSimpleMail(String to, String subject, String content, String... cc) { - SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom(from); - message.setTo(to); - message.setSubject(subject); - message.setText(content); - if (ArrayUtil.isNotEmpty(cc)) { - message.setCc(cc); - } - mailSender.send(message); - } - - /** - * 发送HTML邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - mailSender.send(message); - } - - /** - * 发送带附件的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param filePath 附件地址 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - FileSystemResource file = new FileSystemResource(new File(filePath)); - String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); - helper.addAttachment(fileName, file); - - mailSender.send(message); - } - - /** - * 发送正文中有静态资源的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param rscPath 静态资源地址 - * @param rscId 静态资源id - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - FileSystemResource res = new FileSystemResource(new File(rscPath)); - helper.addInline(rscId, res); - - mailSender.send(message); - } -} -``` - -## MailServiceTest.java - -```java -/** - *

    - * 邮件测试 - *

    - * - * @package: com.xkcoding.email.service - * @description: 邮件测试 - * @author: yangkai.shen - * @date: Created in 2018/11/21 13:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class MailServiceTest extends SpringBootDemoEmailApplicationTests { - @Autowired - private MailService mailService; - @Autowired - private TemplateEngine templateEngine; - @Autowired - private ApplicationContext context; - - /** - * 测试简单邮件 - */ - @Test - public void sendSimpleMail() { - mailService.sendSimpleMail("237497819@qq.com", "这是一封简单邮件", "这是一封普通的SpringBoot测试邮件"); - } - - /** - * 测试HTML邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendHtmlMail() throws MessagingException { - Context context = new Context(); - context.setVariable("project", "Spring Boot Demo"); - context.setVariable("author", "Yangkai.Shen"); - context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); - - String emailTemplate = templateEngine.process("welcome", context); - mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); - } - - /** - * 测试HTML邮件,自定义模板目录 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendHtmlMail2() throws MessagingException { - - SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); - templateResolver.setApplicationContext(context); - templateResolver.setCacheable(false); - templateResolver.setPrefix("classpath:/email/"); - templateResolver.setSuffix(".html"); - - templateEngine.setTemplateResolver(templateResolver); - - Context context = new Context(); - context.setVariable("project", "Spring Boot Demo"); - context.setVariable("author", "Yangkai.Shen"); - context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); - - String emailTemplate = templateEngine.process("test", context); - mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); - } - - /** - * 测试附件邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendAttachmentsMail() throws MessagingException { - URL resource = ResourceUtil.getResource("static/xkcoding.png"); - mailService.sendAttachmentsMail("237497819@qq.com", "这是一封带附件的邮件", "邮件中有附件,请注意查收!", resource.getPath()); - } - - /** - * 测试静态资源邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendResourceMail() throws MessagingException { - String rscId = "xkcoding"; - String content = "这是带静态资源的邮件
    "; - URL resource = ResourceUtil.getResource("static/xkcoding.png"); - mailService.sendResourceMail("237497819@qq.com", "这是一封带静态资源的邮件", content, resource.getPath(), rscId); - } -} -``` - -## welcome.html - -> 此文件为邮件模板,位于 resources/templates 目录下 - -```html - - - - - SpringBootDemo(入门SpringBoot的首选Demo) - - - -
    -

    欢迎使用 - Powered By

    - - -
    - 如果对你有帮助,请任意打赏 -
    -
    -
    -
    -
    -
    - -
    -
    微信打赏
    -
    -
    -
    -
    -
    支付宝打赏
    -
    -
    -
    -
    - -
    - - -``` - -## 参考 - -- Spring Boot 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-email -- Spring Boot 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/integration.html#mail \ No newline at end of file diff --git a/spring-boot-demo-email/pom.xml b/spring-boot-demo-email/pom.xml deleted file mode 100644 index 49adcb8..0000000 --- a/spring-boot-demo-email/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-email - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-email - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.1 - - - - - - org.springframework.boot - spring-boot-starter-mail - - - - - com.github.ulisesbocchio - jasypt-spring-boot-starter - ${jasypt.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - - spring-boot-demo-email - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java b/spring-boot-demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java deleted file mode 100644 index a02cd8c..0000000 --- a/spring-boot-demo-email/src/main/java/com/xkcoding/email/SpringBootDemoEmailApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.email; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.email - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/4 22:38 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoEmailApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoEmailApplication.class, args); - } -} diff --git a/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/MailService.java b/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/MailService.java deleted file mode 100644 index 421d310..0000000 --- a/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/MailService.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.email.service; - -import javax.mail.MessagingException; - -/** - *

    - * 邮件接口 - *

    - * - * @package: com.xkcoding.email.service - * @description: 邮件接口 - * @author: yangkai.shen - * @date: Created in 2018/11/21 11:16 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface MailService { - /** - * 发送文本邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - */ - void sendSimpleMail(String to, String subject, String content, String... cc); - - /** - * 发送HTML邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException; - - /** - * 发送带附件的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param filePath 附件地址 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException; - - /** - * 发送正文中有静态资源的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param rscPath 静态资源地址 - * @param rscId 静态资源id - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException; - -} \ No newline at end of file diff --git a/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java b/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java deleted file mode 100644 index b9e7ec7..0000000 --- a/spring-boot-demo-email/src/main/java/com/xkcoding/email/service/impl/MailServiceImpl.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.xkcoding.email.service.impl; - -import cn.hutool.core.util.ArrayUtil; -import com.xkcoding.email.service.MailService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.FileSystemResource; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.stereotype.Service; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; -import java.io.File; - -/** - *

    - * 邮件接口 - *

    - * - * @package: com.xkcoding.email.service.impl - * @description: 邮件接口 - * @author: yangkai.shen - * @date: Created in 2018/11/21 13:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class MailServiceImpl implements MailService { - @Autowired - private JavaMailSender mailSender; - @Value("${spring.mail.username}") - private String from; - - /** - * 发送文本邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - */ - @Override - public void sendSimpleMail(String to, String subject, String content, String... cc) { - SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom(from); - message.setTo(to); - message.setSubject(subject); - message.setText(content); - if (ArrayUtil.isNotEmpty(cc)) { - message.setCc(cc); - } - mailSender.send(message); - } - - /** - * 发送HTML邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - mailSender.send(message); - } - - /** - * 发送带附件的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param filePath 附件地址 - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - FileSystemResource file = new FileSystemResource(new File(filePath)); - String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); - helper.addAttachment(fileName, file); - - mailSender.send(message); - } - - /** - * 发送正文中有静态资源的邮件 - * - * @param to 收件人地址 - * @param subject 邮件主题 - * @param content 邮件内容 - * @param rscPath 静态资源地址 - * @param rscId 静态资源id - * @param cc 抄送地址 - * @throws MessagingException 邮件发送异常 - */ - @Override - public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(from); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(content, true); - if (ArrayUtil.isNotEmpty(cc)) { - helper.setCc(cc); - } - FileSystemResource res = new FileSystemResource(new File(rscPath)); - helper.addInline(rscId, res); - - mailSender.send(message); - } -} diff --git a/spring-boot-demo-email/src/test/java/com/xkcoding/email/PasswordTest.java b/spring-boot-demo-email/src/test/java/com/xkcoding/email/PasswordTest.java deleted file mode 100644 index d6c0955..0000000 --- a/spring-boot-demo-email/src/test/java/com/xkcoding/email/PasswordTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.email; - -import org.jasypt.encryption.StringEncryptor; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - *

    - * 数据库密码测试 - *

    - * - * @author yangkai.shen - * @date Created in 2019/8/27 16:15 - */ -public class PasswordTest extends SpringBootDemoEmailApplicationTests { - @Autowired - private StringEncryptor encryptor; - - /** - * 生成加密密码 - */ - @Test - public void testGeneratePassword() { - // 你的邮箱密码 - String password = "Just4Test!"; - // 加密后的密码(注意:配置上去的时候需要加 ENC(加密密码)) - String encryptPassword = encryptor.encrypt(password); - String decryptPassword = encryptor.decrypt(encryptPassword); - - System.out.println("password = " + password); - System.out.println("encryptPassword = " + encryptPassword); - System.out.println("decryptPassword = " + decryptPassword); - } -} diff --git a/spring-boot-demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java b/spring-boot-demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java deleted file mode 100644 index c068893..0000000 --- a/spring-boot-demo-email/src/test/java/com/xkcoding/email/SpringBootDemoEmailApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.email; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoEmailApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java b/spring-boot-demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java deleted file mode 100644 index 52b7fd9..0000000 --- a/spring-boot-demo-email/src/test/java/com/xkcoding/email/service/MailServiceTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.xkcoding.email.service; - -import cn.hutool.core.io.resource.ResourceUtil; -import com.xkcoding.email.SpringBootDemoEmailApplicationTests; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; - -import javax.mail.MessagingException; -import java.net.URL; - -/** - *

    - * 邮件测试 - *

    - * - * @package: com.xkcoding.email.service - * @description: 邮件测试 - * @author: yangkai.shen - * @date: Created in 2018/11/21 13:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class MailServiceTest extends SpringBootDemoEmailApplicationTests { - @Autowired - private MailService mailService; - @Autowired - private TemplateEngine templateEngine; - @Autowired - private ApplicationContext context; - - /** - * 测试简单邮件 - */ - @Test - public void sendSimpleMail() { - mailService.sendSimpleMail("237497819@qq.com", "这是一封简单邮件", "这是一封普通的SpringBoot测试邮件"); - } - - /** - * 测试HTML邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendHtmlMail() throws MessagingException { - Context context = new Context(); - context.setVariable("project", "Spring Boot Demo"); - context.setVariable("author", "Yangkai.Shen"); - context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); - - String emailTemplate = templateEngine.process("welcome", context); - mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); - } - - /** - * 测试HTML邮件,自定义模板目录 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendHtmlMail2() throws MessagingException { - - SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); - templateResolver.setApplicationContext(context); - templateResolver.setCacheable(false); - templateResolver.setPrefix("classpath:/email/"); - templateResolver.setSuffix(".html"); - - templateEngine.setTemplateResolver(templateResolver); - - Context context = new Context(); - context.setVariable("project", "Spring Boot Demo"); - context.setVariable("author", "Yangkai.Shen"); - context.setVariable("url", "https://github.com/xkcoding/spring-boot-demo"); - - String emailTemplate = templateEngine.process("test", context); - mailService.sendHtmlMail("237497819@qq.com", "这是一封模板HTML邮件", emailTemplate); - } - - /** - * 测试附件邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendAttachmentsMail() throws MessagingException { - URL resource = ResourceUtil.getResource("static/xkcoding.png"); - mailService.sendAttachmentsMail("237497819@qq.com", "这是一封带附件的邮件", "邮件中有附件,请注意查收!", resource.getPath()); - } - - /** - * 测试静态资源邮件 - * - * @throws MessagingException 邮件异常 - */ - @Test - public void sendResourceMail() throws MessagingException { - String rscId = "xkcoding"; - String content = "这是带静态资源的邮件
    "; - URL resource = ResourceUtil.getResource("static/xkcoding.png"); - mailService.sendResourceMail("237497819@qq.com", "这是一封带静态资源的邮件", content, resource.getPath(), rscId); - } -} diff --git a/spring-boot-demo-exception-handler/README.md b/spring-boot-demo-exception-handler/README.md deleted file mode 100644 index 10d64be..0000000 --- a/spring-boot-demo-exception-handler/README.md +++ /dev/null @@ -1,270 +0,0 @@ -# spring-boot-demo-exception-handler - -> 此 demo 演示了如何在Spring Boot中进行统一的异常处理,包括了两种方式的处理:第一种对常见API形式的接口进行异常处理,统一封装返回格式;第二种是对模板页面请求的异常处理,统一处理错误页面。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-exception-handler - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-exception-handler - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-exception-handler - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## ApiResponse.java - -> 统一的API格式返回封装,里面涉及到的 `BaseException` 和`Status` 这两个类,具体代码见 demo。 - -```java -/** - *

    - * 通用的 API 接口封装 - *

    - * - * @package: com.xkcoding.exception.handler.model - * @description: 通用的 API 接口封装 - * @author: yangkai.shen - * @date: Created in 2018/10/2 8:57 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ApiResponse { - /** - * 状态码 - */ - private Integer code; - - /** - * 返回内容 - */ - private String message; - - /** - * 返回数据 - */ - private Object data; - - /** - * 无参构造函数 - */ - private ApiResponse() { - - } - - /** - * 全参构造函数 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - */ - private ApiResponse(Integer code, String message, Object data) { - this.code = code; - this.message = message; - this.data = data; - } - - /** - * 构造一个自定义的API返回 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse of(Integer code, String message, Object data) { - return new ApiResponse(code, message, data); - } - - /** - * 构造一个成功且带数据的API返回 - * - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofSuccess(Object data) { - return ofStatus(Status.OK, data); - } - - /** - * 构造一个成功且自定义消息的API返回 - * - * @param message 返回内容 - * @return ApiResponse - */ - public static ApiResponse ofMessage(String message) { - return of(Status.OK.getCode(), message, null); - } - - /** - * 构造一个有状态的API返回 - * - * @param status 状态 {@link Status} - * @return ApiResponse - */ - public static ApiResponse ofStatus(Status status) { - return ofStatus(status, null); - } - - /** - * 构造一个有状态且带数据的API返回 - * - * @param status 状态 {@link Status} - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofStatus(Status status, Object data) { - return of(status.getCode(), status.getMessage(), data); - } - - /** - * 构造一个异常且带数据的API返回 - * - * @param t 异常 - * @param data 返回数据 - * @param {@link BaseException} 的子类 - * @return ApiResponse - */ - public static ApiResponse ofException(T t, Object data) { - return of(t.getCode(), t.getMessage(), data); - } - - /** - * 构造一个异常且带数据的API返回 - * - * @param t 异常 - * @param {@link BaseException} 的子类 - * @return ApiResponse - */ - public static ApiResponse ofException(T t) { - return ofException(t, null); - } -} -``` - -## DemoExceptionHandler.java - -```java -/** - *

    - * 统一异常处理 - *

    - * - * @package: com.xkcoding.exception.handler.handler - * @description: 统一异常处理 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:26 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ControllerAdvice -@Slf4j -public class DemoExceptionHandler { - private static final String DEFAULT_ERROR_VIEW = "error"; - - /** - * 统一 json 异常处理 - * - * @param exception JsonException - * @return 统一返回 json 格式 - */ - @ExceptionHandler(value = JsonException.class) - @ResponseBody - public ApiResponse jsonErrorHandler(JsonException exception) { - log.error("【JsonException】:{}", exception.getMessage()); - return ApiResponse.ofException(exception); - } - - /** - * 统一 页面 异常处理 - * - * @param exception PageException - * @return 统一跳转到异常页面 - */ - @ExceptionHandler(value = PageException.class) - public ModelAndView pageErrorHandler(PageException exception) { - log.error("【DemoPageException】:{}", exception.getMessage()); - ModelAndView view = new ModelAndView(); - view.addObject("message", exception.getMessage()); - view.setViewName(DEFAULT_ERROR_VIEW); - return view; - } -} -``` - -## error.html - -> 位于 `src/main/resources/template` 目录下 - -```html - - - - - 统一页面异常处理 - - -

    统一页面异常处理

    -
    - - -``` - diff --git a/spring-boot-demo-exception-handler/pom.xml b/spring-boot-demo-exception-handler/pom.xml deleted file mode 100644 index 14e4c95..0000000 --- a/spring-boot-demo-exception-handler/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-exception-handler - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-exception-handler - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-exception-handler - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java deleted file mode 100644 index ddab733..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.exception.handler; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.exception.handler - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/2 8:49 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoExceptionHandlerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoExceptionHandlerApplication.class, args); - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java deleted file mode 100644 index da60aac..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/constant/Status.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.xkcoding.exception.handler.constant; - -import lombok.Getter; - -/** - *

    - * 状态码封装 - *

    - * - * @package: com.xkcoding.exception.handler.constant - * @description: 状态码封装 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:02 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public enum Status { - /** - * 操作成功 - */ - OK(200, "操作成功"), - - /** - * 未知异常 - */ - UNKNOWN_ERROR(500, "服务器出错啦"); - /** - * 状态码 - */ - private Integer code; - /** - * 内容 - */ - private String message; - - Status(Integer code, String message) { - this.code = code; - this.message = message; - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java deleted file mode 100644 index 48dd975..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/controller/TestController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.exception.handler.controller; - -import com.xkcoding.exception.handler.constant.Status; -import com.xkcoding.exception.handler.exception.JsonException; -import com.xkcoding.exception.handler.exception.PageException; -import com.xkcoding.exception.handler.model.ApiResponse; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; - -/** - *

    - * 测试Controller - *

    - * - * @package: com.xkcoding.exception.handler.controller - * @description: 测试Controller - * @author: yangkai.shen - * @date: Created in 2018/10/2 8:49 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -public class TestController { - - @GetMapping("/json") - @ResponseBody - public ApiResponse jsonException() { - throw new JsonException(Status.UNKNOWN_ERROR); - } - - @GetMapping("/page") - public ModelAndView pageException() { - throw new PageException(Status.UNKNOWN_ERROR); - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java deleted file mode 100644 index d4e037d..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/BaseException.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.exception.handler.exception; - -import com.xkcoding.exception.handler.constant.Status; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

    - * 异常基类 - *

    - * - * @package: com.xkcoding.exception.handler.exception - * @description: 异常基类 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:31 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class BaseException extends RuntimeException { - private Integer code; - private String message; - - public BaseException(Status status) { - super(status.getMessage()); - this.code = status.getCode(); - this.message = status.getMessage(); - } - - public BaseException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java deleted file mode 100644 index b72ddef..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/JsonException.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.exception.handler.exception; - -import com.xkcoding.exception.handler.constant.Status; -import lombok.Getter; - -/** - *

    - * JSON异常 - *

    - * - * @package: com.xkcoding.exception.handler.exception - * @description: JSON异常 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:18 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public class JsonException extends BaseException { - - public JsonException(Status status) { - super(status); - } - - public JsonException(Integer code, String message) { - super(code, message); - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java deleted file mode 100644 index 102327a..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/exception/PageException.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.exception.handler.exception; - -import com.xkcoding.exception.handler.constant.Status; -import lombok.Getter; - -/** - *

    - * 页面异常 - *

    - * - * @package: com.xkcoding.exception.handler.exception - * @description: 页面异常 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:18 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public class PageException extends BaseException { - - public PageException(Status status) { - super(status); - } - - public PageException(Integer code, String message) { - super(code, message); - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java deleted file mode 100644 index 191bd69..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/handler/DemoExceptionHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.xkcoding.exception.handler.handler; - -import com.xkcoding.exception.handler.exception.JsonException; -import com.xkcoding.exception.handler.exception.PageException; -import com.xkcoding.exception.handler.model.ApiResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; - -/** - *

    - * 统一异常处理 - *

    - * - * @package: com.xkcoding.exception.handler.handler - * @description: 统一异常处理 - * @author: yangkai.shen - * @date: Created in 2018/10/2 9:26 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ControllerAdvice -@Slf4j -public class DemoExceptionHandler { - private static final String DEFAULT_ERROR_VIEW = "error"; - - /** - * 统一 json 异常处理 - * - * @param exception JsonException - * @return 统一返回 json 格式 - */ - @ExceptionHandler(value = JsonException.class) - @ResponseBody - public ApiResponse jsonErrorHandler(JsonException exception) { - log.error("【JsonException】:{}", exception.getMessage()); - return ApiResponse.ofException(exception); - } - - /** - * 统一 页面 异常处理 - * - * @param exception PageException - * @return 统一跳转到异常页面 - */ - @ExceptionHandler(value = PageException.class) - public ModelAndView pageErrorHandler(PageException exception) { - log.error("【DemoPageException】:{}", exception.getMessage()); - ModelAndView view = new ModelAndView(); - view.addObject("message", exception.getMessage()); - view.setViewName(DEFAULT_ERROR_VIEW); - return view; - } -} diff --git a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java b/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java deleted file mode 100644 index 4731ce8..0000000 --- a/spring-boot-demo-exception-handler/src/main/java/com/xkcoding/exception/handler/model/ApiResponse.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.xkcoding.exception.handler.model; - -import com.xkcoding.exception.handler.constant.Status; -import com.xkcoding.exception.handler.exception.BaseException; -import lombok.Data; - -/** - *

    - * 通用的 API 接口封装 - *

    - * - * @package: com.xkcoding.exception.handler.model - * @description: 通用的 API 接口封装 - * @author: yangkai.shen - * @date: Created in 2018/10/2 8:57 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ApiResponse { - /** - * 状态码 - */ - private Integer code; - - /** - * 返回内容 - */ - private String message; - - /** - * 返回数据 - */ - private Object data; - - /** - * 无参构造函数 - */ - private ApiResponse() { - - } - - /** - * 全参构造函数 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - */ - private ApiResponse(Integer code, String message, Object data) { - this.code = code; - this.message = message; - this.data = data; - } - - /** - * 构造一个自定义的API返回 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse of(Integer code, String message, Object data) { - return new ApiResponse(code, message, data); - } - - /** - * 构造一个成功且带数据的API返回 - * - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofSuccess(Object data) { - return ofStatus(Status.OK, data); - } - - /** - * 构造一个成功且自定义消息的API返回 - * - * @param message 返回内容 - * @return ApiResponse - */ - public static ApiResponse ofMessage(String message) { - return of(Status.OK.getCode(), message, null); - } - - /** - * 构造一个有状态的API返回 - * - * @param status 状态 {@link Status} - * @return ApiResponse - */ - public static ApiResponse ofStatus(Status status) { - return ofStatus(status, null); - } - - /** - * 构造一个有状态且带数据的API返回 - * - * @param status 状态 {@link Status} - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofStatus(Status status, Object data) { - return of(status.getCode(), status.getMessage(), data); - } - - /** - * 构造一个异常且带数据的API返回 - * - * @param t 异常 - * @param data 返回数据 - * @param {@link BaseException} 的子类 - * @return ApiResponse - */ - public static ApiResponse ofException(T t, Object data) { - return of(t.getCode(), t.getMessage(), data); - } - - /** - * 构造一个异常且带数据的API返回 - * - * @param t 异常 - * @param {@link BaseException} 的子类 - * @return ApiResponse - */ - public static ApiResponse ofException(T t) { - return ofException(t, null); - } -} diff --git a/spring-boot-demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java b/spring-boot-demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java deleted file mode 100644 index 399902c..0000000 --- a/spring-boot-demo-exception-handler/src/test/java/com/xkcoding/exception/handler/SpringBootDemoExceptionHandlerApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.exception.handler; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoExceptionHandlerApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-flyway/pom.xml b/spring-boot-demo-flyway/pom.xml deleted file mode 100644 index 802f67b..0000000 --- a/spring-boot-demo-flyway/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-flyway - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-flyway - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.flywaydb - flyway-core - - - - org.springframework.boot - spring-boot-starter-data-jdbc - - - - mysql - mysql-connector-java - runtime - - - - - spring-boot-demo-flyway - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java b/spring-boot-demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java deleted file mode 100644 index 7a300bc..0000000 --- a/spring-boot-demo-flyway/src/main/java/com/xkcoding/flyway/SpringBootDemoFlywayApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.flyway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @author yangkai.shen - * @date Created in 2020/3/4 18:30 - */ -@SpringBootApplication -public class SpringBootDemoFlywayApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoFlywayApplication.class, args); - } - -} diff --git a/spring-boot-demo-flyway/src/test/java/com/xkcoding/AppTest.java b/spring-boot-demo-flyway/src/test/java/com/xkcoding/AppTest.java deleted file mode 100644 index 16a8ef2..0000000 --- a/spring-boot-demo-flyway/src/test/java/com/xkcoding/AppTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding; - -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -/** - * Unit test for simple App. - */ -public class AppTest -{ - /** - * Rigorous Test :-) - */ - @Test - public void shouldAnswerWithTrue() - { - assertTrue( true ); - } -} diff --git a/spring-boot-demo-graylog/pom.xml b/spring-boot-demo-graylog/pom.xml deleted file mode 100644 index 167e62b..0000000 --- a/spring-boot-demo-graylog/pom.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-graylog - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-graylog - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - de.siegmar - logback-gelf - 2.0.0 - - - - - spring-boot-demo-graylog - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java b/spring-boot-demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java deleted file mode 100644 index e5570ae..0000000 --- a/spring-boot-demo-graylog/src/main/java/com/xkcoding/graylog/SpringBootDemoGraylogApplication.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.graylog; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.graylog - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-04-23 09:43 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoGraylogApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoGraylogApplication.class, args); - } - -} diff --git a/spring-boot-demo-helloworld/README.md b/spring-boot-demo-helloworld/README.md deleted file mode 100644 index 1e42f53..0000000 --- a/spring-boot-demo-helloworld/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# spring-boot-demo-helloworld - -## Runing spring boot demo helloworld - -```sh - $ mvn spring-boot:run -``` -## -> 本 demo 演示如何使用 Spring Boot 写一个hello world - -### pom.xml -```xml - - - 4.0.0 - - spring-boot-demo-helloworld - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-helloworld - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-helloworld - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### SpringBootDemoHelloworldApplication.java - -```java -/** - *

    - * SpringBoot启动类 - *

    - * - * @package: com.xkcoding.helloworld - * @description: SpringBoot启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/28 2:49 PM - * @copyright: Copyright (c) - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@RestController -public class SpringBootDemoHelloworldApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoHelloworldApplication.class, args); - } - - /** - * Hello,World - * - * @param who 参数,非必须 - * @return Hello, ${who} - */ - @GetMapping("/hello") - public String sayHello(@RequestParam(required = false, name = "who") String who) { - if (StrUtil.isBlank(who)) { - who = "World"; - } - return StrUtil.format("Hello, {}!", who); - } -} -``` - -### application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -``` - diff --git a/spring-boot-demo-helloworld/pom.xml b/spring-boot-demo-helloworld/pom.xml deleted file mode 100644 index aef8a55..0000000 --- a/spring-boot-demo-helloworld/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-helloworld - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-helloworld - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-helloworld - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java b/spring-boot-demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java deleted file mode 100644 index b71825c..0000000 --- a/spring-boot-demo-helloworld/src/main/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplication.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.helloworld; - -import cn.hutool.core.util.StrUtil; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * SpringBoot启动类 - *

    - * - * @package: com.xkcoding.helloworld - * @description: SpringBoot启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/28 2:49 PM - * @copyright: Copyright (c) - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@RestController -public class SpringBootDemoHelloworldApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoHelloworldApplication.class, args); - } - - /** - * Hello,World - * - * @param who 参数,非必须 - * @return Hello, ${who} - */ - @GetMapping("/hello") - public String sayHello(@RequestParam(required = false, name = "who") String who) { - if (StrUtil.isBlank(who)) { - who = "World"; - } - return StrUtil.format("Hello, {}!", who); - } -} diff --git a/spring-boot-demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java b/spring-boot-demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java deleted file mode 100644 index d4afb69..0000000 --- a/spring-boot-demo-helloworld/src/test/java/com/xkcoding/helloworld/SpringBootDemoHelloworldApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.helloworld; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoHelloworldApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-https/README.md b/spring-boot-demo-https/README.md deleted file mode 100644 index c2078b0..0000000 --- a/spring-boot-demo-https/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# spring-boot-demo-https - -> 此 demo 主要演示了 Spring Boot 如何集成 https - -## 1. 生成证书 - -首先使用 jdk 自带的 keytool 命令生成证书复制到项目的 `resources` 目录下(生成的证书一般在用户目录下 C:\Users\Administrator\server.keystore) - -> 自己生成的证书浏览器会有危险提示,去ssl网站上使用金钱申请则不会 - -![ssl 命令截图](ssl.png) - -## 2. 添加配置 - -1. 在配置文件配置生成的证书 - -```yaml -server: - ssl: - # 证书路径 - key-store: classpath:server.keystore - key-alias: tomcat - enabled: true - key-store-type: JKS - #与申请时输入一致 - key-store-password: 123456 - # 浏览器默认端口 和 80 类似 - port: 443 -``` - -2. 配置 Tomcat - -```java -/** - *

    - * HTTPS 配置类 - *

    - * - * @author yangkai.shen - * @date Created in 2020/1/19 10:31 - */ -@Configuration -public class HttpsConfig { - /** - * 配置 http(80) -> 强制跳转到 https(443) - */ - @Bean - public Connector connector() { - Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); - connector.setScheme("http"); - connector.setPort(80); - connector.setSecure(false); - connector.setRedirectPort(443); - return connector; - } - - @Bean - public TomcatServletWebServerFactory tomcatServletWebServerFactory(Connector connector) { - TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { - @Override - protected void postProcessContext(Context context) { - SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setUserConstraint("CONFIDENTIAL"); - SecurityCollection collection = new SecurityCollection(); - collection.addPattern("/*"); - securityConstraint.addCollection(collection); - context.addConstraint(securityConstraint); - } - }; - tomcat.addAdditionalTomcatConnectors(connector); - return tomcat; - } -} -``` - -## 3. 测试 - -启动项目,浏览器访问 http://localhost 将自动跳转到 https://localhost - -## 4. 参考 - -- `keytool`命令参考 - -```bash -$ keytool --help -密钥和证书管理工具 - -命令: - - -certreq 生成证书请求 - -changealias 更改条目的别名 - -delete 删除条目 - -exportcert 导出证书 - -genkeypair 生成密钥对 - -genseckey 生成密钥 - -gencert 根据证书请求生成证书 - -importcert 导入证书或证书链 - -importpass 导入口令 - -importkeystore 从其他密钥库导入一个或所有条目 - -keypasswd 更改条目的密钥口令 - -list 列出密钥库中的条目 - -printcert 打印证书内容 - -printcertreq 打印证书请求的内容 - -printcrl 打印 CRL 文件的内容 - -storepasswd 更改密钥库的存储口令 - -使用 "keytool -command_name -help" 获取 command_name 的用法 -``` - -- [Java Keytool工具简介](https://blog.csdn.net/liumiaocn/article/details/61921014) \ No newline at end of file diff --git a/spring-boot-demo-https/pom.xml b/spring-boot-demo-https/pom.xml deleted file mode 100644 index bfd1b35..0000000 --- a/spring-boot-demo-https/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-https - 0.0.1-SNAPSHOT - spring-boot-demo-https - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java b/spring-boot-demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java deleted file mode 100644 index 8a96069..0000000 --- a/spring-boot-demo-https/src/main/java/com/xkcoding/https/SpringBootDemoHttpsApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.https; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @author Chen.Chao - * @date Created in 2020/1/12 10:31 - */ -@SpringBootApplication -public class SpringBootDemoHttpsApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoHttpsApplication.class, args); - } - -} diff --git a/spring-boot-demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java b/spring-boot-demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java deleted file mode 100644 index 3d8b9a3..0000000 --- a/spring-boot-demo-https/src/main/java/com/xkcoding/https/config/HttpsConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.https.config; - -import org.apache.catalina.Context; -import org.apache.catalina.connector.Connector; -import org.apache.tomcat.util.descriptor.web.SecurityCollection; -import org.apache.tomcat.util.descriptor.web.SecurityConstraint; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * HTTPS 配置类 - *

    - * - * @author Chen.Chao - * @date Created in 2020/1/12 10:31 - */ -@Configuration -public class HttpsConfig { - /** - * 配置 http(80) -> 强制跳转到 https(443) - */ - @Bean - public Connector connector() { - Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); - connector.setScheme("http"); - connector.setPort(80); - connector.setSecure(false); - connector.setRedirectPort(443); - return connector; - } - - @Bean - public TomcatServletWebServerFactory tomcatServletWebServerFactory(Connector connector) { - TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { - @Override - protected void postProcessContext(Context context) { - SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setUserConstraint("CONFIDENTIAL"); - SecurityCollection collection = new SecurityCollection(); - collection.addPattern("/*"); - securityConstraint.addCollection(collection); - context.addConstraint(securityConstraint); - } - }; - tomcat.addAdditionalTomcatConnectors(connector); - return tomcat; - } -} diff --git a/spring-boot-demo-https/src/main/resources/static/index.html b/spring-boot-demo-https/src/main/resources/static/index.html deleted file mode 100644 index 067bc53..0000000 --- a/spring-boot-demo-https/src/main/resources/static/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - spring boot demo https - - -

    - spring boot demo https -

    - - - diff --git a/spring-boot-demo-ldap/README.md b/spring-boot-demo-ldap/README.md deleted file mode 100644 index 3dde0ad..0000000 --- a/spring-boot-demo-ldap/README.md +++ /dev/null @@ -1,393 +0,0 @@ -# spring-boot-demo-ldap - -> 此 demo 主要演示了 Spring Boot 如何集成 `spring-boot-starter-data-ldap` 完成对 LDAP 的基本 CURD操作, 并给出以登录为实战的 API 示例 - -## docker openldap 安装步骤 - -> 参考: https://github.com/osixia/docker-openldap -1. 下载镜像: `docker pull osixia/openldap:1.2.5` - -2. 运行容器: `docker run -p 389:389 -p 636:636 --name my-openldap --detach osixia/openldap:1.2.5` - -3. 添加管理员: `docker exec my-openldap ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin` - -4. 停止容器:`docker stop my-openldap` - -5. 启动容器:`docker start my-openldap` - - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-ldap - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ldap - Demo project for Spring Boot - - - spring-boot-demo - com.xkcoding - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter-data-ldap - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.projectlombok - lombok - true - provided - - - - -``` - -## application.yml - -```yaml -spring: - ldap: - urls: ldap://localhost:389 - base: dc=example,dc=org - username: cn=admin,dc=example,dc=org - password: admin -``` - -## Person.java - -> 实体类 -> @Entry 注解 映射ldap对象关系 -```java -/** - * People - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 0:51 - */ -@Data -@Entry( - base = "ou=people", - objectClasses = {"posixAccount", "inetOrgPerson", "top"} -) -public class Person implements Serializable { - - private static final long serialVersionUID = -7946768337975852352L; - - @Id - private Name id; - - private String uidNumber; - - private String gidNumber; - - /** - * 用户名 - */ - @DnAttribute(value = "uid", index = 1) - private String uid; - - /** - * 姓名 - */ - @Attribute(name = "cn") - private String personName; - - /** - * 密码 - */ - private String userPassword; - - /** - * 名字 - */ - private String givenName; - - /** - * 姓氏 - */ - @Attribute(name = "sn") - private String surname; - - /** - * 邮箱 - */ - private String mail; - - /** - * 职位 - */ - private String title; - - /** - * 根目录 - */ - private String homeDirectory; - - /** - * loginShell - */ - private String loginShell; -} -``` - -## PersonRepository.java -> person 数据持久层 -```java -/** - * PersonRepository - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:02 - */ -@Repository -public interface PersonRepository extends CrudRepository { - - /** - * 根据用户名查找 - * - * @param uid 用户名 - * @return com.xkcoding.ldap.entity.Person - */ - Person findByUid(String uid); -} -``` - -## PersonService.java -> 数据操作服务 -```java -/** - * PersonService - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:05 - */ -public interface PersonService { - - /** - * 登录 - * - * @param request {@link LoginRequest} - * @return {@link Result} - */ - Result login(LoginRequest request); - - /** - * 查询全部 - * - * @return {@link Result} - */ - Result listAllPerson(); - - /** - * 保存 - * - * @param person {@link Person} - */ - void save(Person person); - - /** - * 删除 - * - * @param person {@link Person} - */ - void delete(Person person); - -} -``` - -## PersonServiceImpl.java -> person数据操作服务具体逻辑实现类 - -```java -/** - * PersonServiceImpl - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:05 - */ -@Slf4j -@Service -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class PersonServiceImpl implements PersonService { - private final PersonRepository personRepository; - - /** - * 登录 - * - * @param request {@link LoginRequest} - * @return {@link Result} - */ - @Override - public Result login(LoginRequest request) { - log.info("IN LDAP auth"); - - Person user = personRepository.findByUid(request.getUsername()); - - try { - if (ObjectUtils.isEmpty(user)) { - throw new ServiceException("用户名或密码错误,请重新尝试"); - } else { - user.setUserPassword(LdapUtils.asciiToString(user.getUserPassword())); - if (!LdapUtils.verify(user.getUserPassword(), request.getPassword())) { - throw new ServiceException("用户名或密码错误,请重新尝试"); - } - } - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - log.info("user info:{}", user); - return Result.success(user); - } - - /** - * 查询全部 - * - * @return {@link Result} - */ - @Override - public Result listAllPerson() { - Iterable personList = personRepository.findAll(); - personList.forEach(person -> person.setUserPassword(LdapUtils.asciiToString(person.getUserPassword()))); - return Result.success(personList); - } - - /** - * 保存 - * - * @param person {@link Person} - */ - @Override - public void save(Person person) { - Person p = personRepository.save(person); - log.info("用户{}保存成功", p.getUid()); - } - - /** - * 删除 - * - * @param person {@link Person} - */ - @Override - public void delete(Person person) { - personRepository.delete(person); - log.info("删除用户{}成功", person.getUid()); - } - -} -``` - -## LdapDemoApplicationTests.java -> 测试 -```java -/** - * LdapDemoApplicationTest - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:06 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class LdapDemoApplicationTests { - - @Resource - private PersonService personService; - - @Test - public void contextLoads() { - } - - /** - * 测试查询单个 - */ - @Test - public void loginTest() { - LoginRequest loginRequest = LoginRequest.builder().username("wangwu").password("123456").build(); - Result login = personService.login(loginRequest); - System.out.println(login); - } - - /** - * 测试查询列表 - */ - @Test - public void listAllPersonTest() { - Result result = personService.listAllPerson(); - System.out.println(result); - } - - /** - * 测试保存 - */ - @Test - public void saveTest() { - Person person = new Person(); - - person.setUid("zhaosi"); - - person.setSurname("赵"); - person.setGivenName("四"); - person.setUserPassword("123456"); - - // required field - person.setPersonName("赵四"); - person.setUidNumber("666"); - person.setGidNumber("666"); - person.setHomeDirectory("/home/zhaosi"); - person.setLoginShell("/bin/bash"); - - personService.save(person); - } - - /** - * 测试删除 - */ - @Test - public void deleteTest() { - Person person = new Person(); - person.setUid("zhaosi"); - - personService.delete(person); - } - -} -``` - -## 其余代码参见本 demo - -## 参考 - -spring-data-ldap 官方文档: https://docs.spring.io/spring-data/ldap/docs/2.1.10.RELEASE/reference/html/ diff --git a/spring-boot-demo-ldap/pom.xml b/spring-boot-demo-ldap/pom.xml deleted file mode 100644 index f5be7fa..0000000 --- a/spring-boot-demo-ldap/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-ldap - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ldap - Demo project for Spring Boot - - - spring-boot-demo - com.xkcoding - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter-data-ldap - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.projectlombok - lombok - true - provided - - - - diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java deleted file mode 100644 index f463f85..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/LdapDemoApplication.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xkcoding.ldap; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * LdapDemoApplication Ldap demo 启动类 - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 0:37 - */ -@SpringBootApplication -public class LdapDemoApplication { - - public static void main(String[] args) { - SpringApplication.run(LdapDemoApplication.class, args); - } -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java deleted file mode 100644 index 0e8aa40..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/Result.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.xkcoding.ldap.api; - -import lombok.Data; -import org.springframework.lang.Nullable; - -import java.io.Serializable; - -/** - * Result - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:44 - */ -@Data -public class Result implements Serializable { - - private static final long serialVersionUID = 1696194043024336235L; - - /** - * 错误码 - */ - private int errcode; - - /** - * 错误信息 - */ - private String errmsg; - - /** - * 响应数据 - */ - private T data; - - public Result() { - } - - private Result(ResultCode resultCode) { - this(resultCode.code, resultCode.msg); - } - - private Result(ResultCode resultCode, T data) { - this(resultCode.code, resultCode.msg, data); - } - - private Result(int errcode, String errmsg) { - this(errcode, errmsg, null); - } - - private Result(int errcode, String errmsg, T data) { - this.errcode = errcode; - this.errmsg = errmsg; - this.data = data; - } - - - - /** - * 返回成功 - * - * @param 泛型标记 - * @return 响应信息 {@code Result} - */ - public static Result success() { - return new Result<>(ResultCode.SUCCESS); - } - - - /** - * 返回成功-携带数据 - * - * @param data 响应数据 - * @param 泛型标记 - * @return 响应信息 {@code Result} - */ - public static Result success(@Nullable T data) { - return new Result<>(ResultCode.SUCCESS, data); - } - - - - - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java deleted file mode 100644 index 621e875..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/api/ResultCode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.ldap.api; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * ResultCode - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:47 - */ -@Getter -@AllArgsConstructor -public enum ResultCode { - - /** - * 接口调用成功 - */ - SUCCESS(0, "Request Successful"), - - /** - * 服务器暂不可用,建议稍候重试。建议重试次数不超过3次。 - */ - FAILURE(-1, "System Busy"); - - final int code; - - final String msg; - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java deleted file mode 100644 index 38029b2..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/entity/Person.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xkcoding.ldap.entity; - -import lombok.Data; -import org.springframework.ldap.odm.annotations.Attribute; -import org.springframework.ldap.odm.annotations.DnAttribute; -import org.springframework.ldap.odm.annotations.Entry; -import org.springframework.ldap.odm.annotations.Id; - -import javax.naming.Name; -import java.io.Serializable; - -/** - * People - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 0:51 - */ -@Data -@Entry( - base = "ou=people", - objectClasses = {"posixAccount", "inetOrgPerson", "top"} -) -public class Person implements Serializable { - - private static final long serialVersionUID = -7946768337975852352L; - - @Id - private Name id; - - /** - * 用户id - */ - private String uidNumber; - - /** - * 用户名 - */ - @DnAttribute(value = "uid", index = 1) - private String uid; - - /** - * 姓名 - */ - @Attribute(name = "cn") - private String personName; - - /** - * 密码 - */ - private String userPassword; - - /** - * 名字 - */ - private String givenName; - - /** - * 姓氏 - */ - @Attribute(name = "sn") - private String surname; - - /** - * 邮箱 - */ - private String mail; - - /** - * 职位 - */ - private String title; - - /** - * 部门 - */ - private String departmentNumber; - - /** - * 部门id - */ - private String gidNumber; - - /** - * 根目录 - */ - private String homeDirectory; - - /** - * loginShell - */ - private String loginShell; - - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java deleted file mode 100644 index a600b20..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/exception/ServiceException.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.ldap.exception; - -import com.xkcoding.ldap.api.ResultCode; -import lombok.Getter; - -/** - * ServiceException - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:53 - */ -public class ServiceException extends RuntimeException { - - @Getter - private int errcode; - - @Getter - private String errmsg; - - public ServiceException(ResultCode resultCode) { - this(resultCode.getCode(), resultCode.getMsg()); - } - - public ServiceException(String message) { - super(message); - } - - public ServiceException(Integer errcode, String errmsg) { - super(errmsg); - this.errcode = errcode; - this.errmsg = errmsg; - } - - public ServiceException(String message, Throwable cause) { - super(message, cause); - } - - public ServiceException(Throwable cause) { - super(cause); - } - - public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java deleted file mode 100644 index 89799f0..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/repository/PersonRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.ldap.repository; - -import com.xkcoding.ldap.entity.Person; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -import javax.naming.Name; - -/** - * PersonRepository - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:02 - */ -@Repository -public interface PersonRepository extends CrudRepository { - - /** - * 根据用户名查找 - * - * @param uid 用户名 - * @return com.xkcoding.ldap.entity.Person - */ - Person findByUid(String uid); -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java deleted file mode 100644 index c1d2380..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/request/LoginRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.ldap.request; - -import lombok.Builder; -import lombok.Data; - -/** - * LoginRequest - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:50 - */ -@Data -@Builder -public class LoginRequest { - - private String username; - - private String password; - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java deleted file mode 100644 index bb45632..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/PersonService.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.ldap.service; - -import com.xkcoding.ldap.api.Result; -import com.xkcoding.ldap.entity.Person; -import com.xkcoding.ldap.request.LoginRequest; - -/** - * PersonService - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:05 - */ -public interface PersonService { - - /** - * 登录 - * - * @param request {@link LoginRequest} - * @return {@link Result} - */ - Result login(LoginRequest request); - - /** - * 查询全部 - * - * @return {@link Result} - */ - Result listAllPerson(); - - /** - * 保存 - * - * @param person {@link Person} - */ - void save(Person person); - - /** - * 删除 - * - * @param person {@link Person} - */ - void delete(Person person); - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java deleted file mode 100644 index 363f65e..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/service/impl/PersonServiceImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.xkcoding.ldap.service.impl; - -import com.xkcoding.ldap.api.Result; -import com.xkcoding.ldap.entity.Person; -import com.xkcoding.ldap.exception.ServiceException; -import com.xkcoding.ldap.repository.PersonRepository; -import com.xkcoding.ldap.request.LoginRequest; -import com.xkcoding.ldap.service.PersonService; -import com.xkcoding.ldap.util.LdapUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.ObjectUtils; - -import java.security.NoSuchAlgorithmException; - -/** - * PersonServiceImpl - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:05 - */ -@Slf4j -@Service -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class PersonServiceImpl implements PersonService { - private final PersonRepository personRepository; - - /** - * 登录 - * - * @param request {@link LoginRequest} - * @return {@link Result} - */ - @Override - public Result login(LoginRequest request) { - log.info("IN LDAP auth"); - - Person user = personRepository.findByUid(request.getUsername()); - - try { - if (ObjectUtils.isEmpty(user)) { - throw new ServiceException("用户名或密码错误,请重新尝试"); - } else { - user.setUserPassword(LdapUtils.asciiToString(user.getUserPassword())); - if (!LdapUtils.verify(user.getUserPassword(), request.getPassword())) { - throw new ServiceException("用户名或密码错误,请重新尝试"); - } - } - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - log.info("user info:{}", user); - return Result.success(user); - } - - /** - * 查询全部 - * - * @return {@link Result} - */ - @Override - public Result listAllPerson() { - Iterable personList = personRepository.findAll(); - personList.forEach(person -> person.setUserPassword(LdapUtils.asciiToString(person.getUserPassword()))); - return Result.success(personList); - } - - /** - * 保存 - * - * @param person {@link Person} - */ - @Override - public void save(Person person) { - Person p = personRepository.save(person); - log.info("用户{}保存成功", p.getUid()); - } - - /** - * 删除 - * - * @param person {@link Person} - */ - @Override - public void delete(Person person) { - personRepository.delete(person); - log.info("删除用户{}成功", person.getUid()); - } - -} diff --git a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java b/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java deleted file mode 100644 index 606a5a8..0000000 --- a/spring-boot-demo-ldap/src/main/java/com/xkcoding/ldap/util/LdapUtils.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.xkcoding.ldap.util; - -import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * LdapUtils - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:03 - */ -public class LdapUtils { - - /** - * 校验密码 - * - * @param ldapPassword ldap 加密密码 - * @param inputPassword 用户输入 - * @return boolean - * @throws NoSuchAlgorithmException 加解密异常 - */ - public static boolean verify(String ldapPassword, String inputPassword) throws NoSuchAlgorithmException { - - // MessageDigest 提供了消息摘要算法,如 MD5 或 SHA,的功能,这里LDAP使用的是SHA-1 - MessageDigest md = MessageDigest.getInstance("SHA-1"); - - // 取出加密字符 - if (ldapPassword.startsWith("{SSHA}")) { - ldapPassword = ldapPassword.substring(6); - } else if (ldapPassword.startsWith("{SHA}")) { - ldapPassword = ldapPassword.substring(5); - } - // 解码BASE64 - byte[] ldapPasswordByte = Base64.decode(ldapPassword); - byte[] shaCode; - byte[] salt; - - // 前20位是SHA-1加密段,20位后是最初加密时的随机明文 - if (ldapPasswordByte.length <= 20) { - shaCode = ldapPasswordByte; - salt = new byte[0]; - } else { - shaCode = new byte[20]; - salt = new byte[ldapPasswordByte.length - 20]; - System.arraycopy(ldapPasswordByte, 0, shaCode, 0, 20); - System.arraycopy(ldapPasswordByte, 20, salt, 0, salt.length); - } - // 把用户输入的密码添加到摘要计算信息 - md.update(inputPassword.getBytes()); - // 把随机明文添加到摘要计算信息 - md.update(salt); - - // 按SSHA把当前用户密码进行计算 - byte[] inputPasswordByte = md.digest(); - - // 返回校验结果 - return MessageDigest.isEqual(shaCode, inputPasswordByte); - } - - /** - * Ascii转换为字符串 - * - * @param value Ascii串 - * @return 字符串 - */ - public static String asciiToString(String value) { - StringBuilder sbu = new StringBuilder(); - String[] chars = value.split(","); - for (String aChar : chars) { - sbu.append((char) Integer.parseInt(aChar)); - } - return sbu.toString(); - } - -} diff --git a/spring-boot-demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java b/spring-boot-demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java deleted file mode 100644 index 847e8bb..0000000 --- a/spring-boot-demo-ldap/src/test/java/com/xkcoding/ldap/LdapDemoApplicationTests.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.xkcoding.ldap; - -import com.xkcoding.ldap.api.Result; -import com.xkcoding.ldap.entity.Person; -import com.xkcoding.ldap.request.LoginRequest; -import com.xkcoding.ldap.service.PersonService; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import javax.annotation.Resource; - -/** - * LdapDemoApplicationTest - * - * @author fxbin - * @version v1.0 - * @since 2019/8/26 1:06 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class LdapDemoApplicationTests { - - @Resource - private PersonService personService; - - @Test - public void contextLoads() { - } - - /** - * 测试查询单个 - */ - @Test - public void loginTest() { - LoginRequest loginRequest = LoginRequest.builder().username("wangwu").password("123456").build(); - Result login = personService.login(loginRequest); - System.out.println(login); - } - - /** - * 测试查询列表 - */ - @Test - public void listAllPersonTest() { - Result result = personService.listAllPerson(); - System.out.println(result); - } - - /** - * 测试保存 - */ - @Test - public void saveTest() { - Person person = new Person(); - - person.setUid("zhaosi"); - - person.setSurname("赵"); - person.setGivenName("四"); - person.setUserPassword("123456"); - - // required field - person.setPersonName("赵四"); - person.setUidNumber("666"); - person.setGidNumber("666"); - person.setHomeDirectory("/home/zhaosi"); - person.setLoginShell("/bin/bash"); - - personService.save(person); - } - - /** - * 测试删除 - */ - @Test - public void deleteTest() { - Person person = new Person(); - person.setUid("zhaosi"); - - personService.delete(person); - } - -} diff --git a/spring-boot-demo-log-aop/README.md b/spring-boot-demo-log-aop/README.md deleted file mode 100644 index ec00736..0000000 --- a/spring-boot-demo-log-aop/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# spring-boot-demo-log-aop - -> 此 demo 主要是演示如何使用 aop 切面对请求进行日志记录,并且记录 UserAgent 信息。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-log-aop - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-log-aop - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - eu.bitwalker - UserAgentUtils - - - - - spring-boot-demo-log-aop - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## AopLog.java - -```java -/** - *

    - * 使用 aop 切面记录请求日志信息 - *

    - * - * @package: com.xkcoding.log.aop.aspectj - * @description: 使用 aop 切面记录请求日志信息 - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:05 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Aspect -@Component -@Slf4j -public class AopLog { - private static final String START_TIME = "request-start"; - - /** - * 切入点 - */ - @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))") - public void log() { - - } - - /** - * 前置操作 - * - * @param point 切入点 - */ - @Before("log()") - public void beforeLog(JoinPoint point) { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - - HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); - - log.info("【请求 URL】:{}", request.getRequestURL()); - log.info("【请求 IP】:{}", request.getRemoteAddr()); - log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName()); - - Map parameterMap = request.getParameterMap(); - log.info("【请求参数】:{},", JSONUtil.toJsonStr(parameterMap)); - Long start = System.currentTimeMillis(); - request.setAttribute(START_TIME, start); - } - - /** - * 环绕操作 - * - * @param point 切入点 - * @return 原方法返回值 - * @throws Throwable 异常信息 - */ - @Around("log()") - public Object aroundLog(ProceedingJoinPoint point) throws Throwable { - Object result = point.proceed(); - log.info("【返回值】:{}", JSONUtil.toJsonStr(result)); - return result; - } - - /** - * 后置操作 - */ - @AfterReturning("log()") - public void afterReturning() { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); - - Long start = (Long) request.getAttribute(START_TIME); - Long end = System.currentTimeMillis(); - log.info("【请求耗时】:{}毫秒", end - start); - - String header = request.getHeader("User-Agent"); - UserAgent userAgent = UserAgent.parseUserAgentString(header); - log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header); - } -} -``` - -## TestController.java - -```java -/** - *

    - * 测试 Controller - *

    - * - * @package: com.xkcoding.log.aop.controller - * @description: 测试 Controller - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:10 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -public class TestController { - - /** - * 测试方法 - * - * @param who 测试参数 - * @return {@link Dict} - */ - @GetMapping("/test") - public Dict test(String who) { - return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who); - } - -} -``` - diff --git a/spring-boot-demo-log-aop/pom.xml b/spring-boot-demo-log-aop/pom.xml deleted file mode 100644 index 17087e8..0000000 --- a/spring-boot-demo-log-aop/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-log-aop - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-log-aop - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - eu.bitwalker - UserAgentUtils - - - - - spring-boot-demo-log-aop - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java b/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java deleted file mode 100644 index 8240bcc..0000000 --- a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.log.aop; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.log.aop - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:05 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoLogAopApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoLogAopApplication.class, args); - } -} diff --git a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java b/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java deleted file mode 100644 index 9caa858..0000000 --- a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/aspectj/AopLog.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xkcoding.log.aop.aspectj; - -import cn.hutool.json.JSONUtil; -import eu.bitwalker.useragentutils.UserAgent; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Map; -import java.util.Objects; - -/** - *

    - * 使用 aop 切面记录请求日志信息 - *

    - * - * @package: com.xkcoding.log.aop.aspectj - * @description: 使用 aop 切面记录请求日志信息 - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:05 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Aspect -@Component -@Slf4j -public class AopLog { - private static final String START_TIME = "request-start"; - - /** - * 切入点 - */ - @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))") - public void log() { - - } - - /** - * 前置操作 - * - * @param point 切入点 - */ - @Before("log()") - public void beforeLog(JoinPoint point) { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - - HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); - - log.info("【请求 URL】:{}", request.getRequestURL()); - log.info("【请求 IP】:{}", request.getRemoteAddr()); - log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName()); - - Map parameterMap = request.getParameterMap(); - log.info("【请求参数】:{},", JSONUtil.toJsonStr(parameterMap)); - Long start = System.currentTimeMillis(); - request.setAttribute(START_TIME, start); - } - - /** - * 环绕操作 - * - * @param point 切入点 - * @return 原方法返回值 - * @throws Throwable 异常信息 - */ - @Around("log()") - public Object aroundLog(ProceedingJoinPoint point) throws Throwable { - Object result = point.proceed(); - log.info("【返回值】:{}", JSONUtil.toJsonStr(result)); - return result; - } - - /** - * 后置操作 - */ - @AfterReturning("log()") - public void afterReturning() { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); - - Long start = (Long) request.getAttribute(START_TIME); - Long end = System.currentTimeMillis(); - log.info("【请求耗时】:{}毫秒", end - start); - - String header = request.getHeader("User-Agent"); - UserAgent userAgent = UserAgent.parseUserAgentString(header); - log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header); - } -} diff --git a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java b/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java deleted file mode 100644 index 8cff8c4..0000000 --- a/spring-boot-demo-log-aop/src/main/java/com/xkcoding/log/aop/controller/TestController.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.log.aop.controller; - -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.StrUtil; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 测试 Controller - *

    - * - * @package: com.xkcoding.log.aop.controller - * @description: 测试 Controller - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:10 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -public class TestController { - - /** - * 测试方法 - * - * @param who 测试参数 - * @return {@link Dict} - */ - @GetMapping("/test") - public Dict test(String who) { - return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who); - } - -} diff --git a/spring-boot-demo-log-aop/src/main/resources/logback-spring.xml b/spring-boot-demo-log-aop/src/main/resources/logback-spring.xml deleted file mode 100644 index 284bb16..0000000 --- a/spring-boot-demo-log-aop/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - INFO - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - - ERROR - - DENY - - ACCEPT - - - - - - - logs/spring-boot-demo-log-aop/info.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - - - 2MB - - - - - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - Error - - - - - - - logs/spring-boot-demo-log-aop/error.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - 2MB - - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - - - \ No newline at end of file diff --git a/spring-boot-demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java b/spring-boot-demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java deleted file mode 100644 index f2af7df..0000000 --- a/spring-boot-demo-log-aop/src/test/java/com/xkcoding/log/aop/SpringBootDemoLogAopApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.log.aop; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoLogAopApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-logback/README.md b/spring-boot-demo-logback/README.md deleted file mode 100644 index 32a8f70..0000000 --- a/spring-boot-demo-logback/README.md +++ /dev/null @@ -1,183 +0,0 @@ -# spring-boot-demo-logback - -> 此 demo 主要演示了如何使用 logback 记录程序运行过程中的日志,以及如何配置 logback,可以同时生成控制台日志和文件日志记录,文件日志以日期和大小进行拆分生成。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-logback - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-logback - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-logback - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoLogbackApplication.java - -```java -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.logback - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/30 11:16 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@Slf4j -public class SpringBootDemoLogbackApplication { - - public static void main(String[] args) { - ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoLogbackApplication.class, args); - int length = context.getBeanDefinitionNames().length; - log.trace("Spring boot启动初始化了 {} 个 Bean", length); - log.debug("Spring boot启动初始化了 {} 个 Bean", length); - log.info("Spring boot启动初始化了 {} 个 Bean", length); - log.warn("Spring boot启动初始化了 {} 个 Bean", length); - log.error("Spring boot启动初始化了 {} 个 Bean", length); - try { - int i = 0; - int j = 1 / i; - } catch (Exception e) { - log.error("【SpringBootDemoLogbackApplication】启动异常:", e); - } - } -} -``` - -## logback-spring.xml - -```xml - - - - - - INFO - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - - ERROR - - DENY - - ACCEPT - - - - - - - logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - - - 2MB - - - - - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - Error - - - - - - - logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - 2MB - - - - %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n - UTF-8 - - - - - - - - - -``` - diff --git a/spring-boot-demo-logback/pom.xml b/spring-boot-demo-logback/pom.xml deleted file mode 100644 index 05a187f..0000000 --- a/spring-boot-demo-logback/pom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-logback - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-logback - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-logback - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java b/spring-boot-demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java deleted file mode 100644 index 43ed673..0000000 --- a/spring-boot-demo-logback/src/main/java/com/xkcoding/logback/SpringBootDemoLogbackApplication.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.logback; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.logback - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/30 11:16 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@Slf4j -public class SpringBootDemoLogbackApplication { - - public static void main(String[] args) { - ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoLogbackApplication.class, args); - int length = context.getBeanDefinitionNames().length; - log.trace("Spring boot启动初始化了 {} 个 Bean", length); - log.debug("Spring boot启动初始化了 {} 个 Bean", length); - log.info("Spring boot启动初始化了 {} 个 Bean", length); - log.warn("Spring boot启动初始化了 {} 个 Bean", length); - log.error("Spring boot启动初始化了 {} 个 Bean", length); - try { - int i = 0; - int j = 1 / i; - } catch (Exception e) { - log.error("【SpringBootDemoLogbackApplication】启动异常:", e); - } - } -} diff --git a/spring-boot-demo-logback/src/main/resources/logback-spring.xml b/spring-boot-demo-logback/src/main/resources/logback-spring.xml deleted file mode 100644 index 5b24b88..0000000 --- a/spring-boot-demo-logback/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - INFO - - - ${CONSOLE_LOG_PATTERN} - UTF-8 - - - - - - - - ERROR - - DENY - - ACCEPT - - - - - - - logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - - - 2MB - - - - - - - ${FILE_LOG_PATTERN} - UTF-8 - - - - - - - Error - - - - - - - logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log - - 90 - - - 2MB - - - - ${FILE_ERROR_PATTERN} - UTF-8 - - - - - - - - - diff --git a/spring-boot-demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java b/spring-boot-demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java deleted file mode 100644 index b387f30..0000000 --- a/spring-boot-demo-logback/src/test/java/com/xkcoding/logback/SpringBootDemoLogbackApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.logback; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoLogbackApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-mongodb/README.md b/spring-boot-demo-mongodb/README.md deleted file mode 100644 index 390004e..0000000 --- a/spring-boot-demo-mongodb/README.md +++ /dev/null @@ -1,332 +0,0 @@ -# spring-boot-demo-mongodb - -> 此 demo 主要演示了 Spring Boot 如何集成 MongoDB,使用官方的 starter 实现增删改查。 - -## 注意 - -作者编写本demo时,MongoDB 最新版本为 `4.1`,使用 docker 运行,下面是所有步骤: - -1. 下载镜像:`docker pull mongo:4.1` -2. 运行容器:`docker run -d -p 27017:27017 -v /Users/yangkai.shen/docker/mongo/data:/data/db --name mongo-4.1 mongo:4.1` -3. 停止容器:`docker stop mongo-4.1` -4. 启动容器:`docker start mongo-4.1` - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-mongodb - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mongodb - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-mongodb - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -spring: - data: - mongodb: - host: localhost - port: 27017 - database: article_db -logging: - level: - org.springframework.data.mongodb.core: debug -``` - -## Article.java - -```java -/** - *

    - * 文章实体类 - *

    - * - * @package: com.xkcoding.mongodb.model - * @description: 文章实体类 - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:21 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Article { - /** - * 文章id - */ - @Id - private Long id; - - /** - * 文章标题 - */ - private String title; - - /** - * 文章内容 - */ - private String content; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 更新时间 - */ - private Date updateTime; - - /** - * 点赞数量 - */ - private Long thumbUp; - - /** - * 访客数量 - */ - private Long visits; - -} -``` - -## ArticleRepository.java - -```java -/** - *

    - * 文章 Dao - *

    - * - * @package: com.xkcoding.mongodb.repository - * @description: 文章 Dao - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface ArticleRepository extends MongoRepository { - /** - * 根据标题模糊查询 - * - * @param title 标题 - * @return 满足条件的文章列表 - */ - List
    findByTitleLike(String title); -} -``` - -## ArticleRepositoryTest.java - -```java -/** - *

    - * 测试操作 MongoDb - *

    - * - * @package: com.xkcoding.mongodb.repository - * @description: 测试操作 MongoDb - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:35 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class ArticleRepositoryTest extends SpringBootDemoMongodbApplicationTests { - @Autowired - private ArticleRepository articleRepo; - - @Autowired - private MongoTemplate mongoTemplate; - - @Autowired - private Snowflake snowflake; - - /** - * 测试新增 - */ - @Test - public void testSave() { - Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil - .date(), 0L, 0L); - articleRepo.save(article); - log.info("【article】= {}", JSONUtil.toJsonStr(article)); - } - - /** - * 测试新增列表 - */ - @Test - public void testSaveList() { - List
    articles = Lists.newArrayList(); - for (int i = 0; i < 10; i++) { - articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil - .date(), DateUtil.date(), 0L, 0L)); - } - articleRepo.saveAll(articles); - - log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream() - .map(Article::getId) - .collect(Collectors.toList()))); - } - - /** - * 测试更新 - */ - @Test - public void testUpdate() { - articleRepo.findById(1L).ifPresent(article -> { - article.setTitle(article.getTitle() + "更新之后的标题"); - article.setUpdateTime(DateUtil.date()); - articleRepo.save(article); - log.info("【article】= {}", JSONUtil.toJsonStr(article)); - }); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - // 根据主键删除 - articleRepo.deleteById(1L); - - // 全部删除 - articleRepo.deleteAll(); - } - - /** - * 测试点赞数、访客数,使用save方式更新点赞、访客 - */ - @Test - public void testThumbUp() { - articleRepo.findById(1L).ifPresent(article -> { - article.setThumbUp(article.getThumbUp() + 1); - article.setVisits(article.getVisits() + 1); - articleRepo.save(article); - log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits()); - }); - } - - /** - * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客 - */ - @Test - public void testThumbUp2() { - Query query = new Query(); - query.addCriteria(Criteria.where("_id").is(1L)); - Update update = new Update(); - update.inc("thumbUp", 1L); - update.inc("visits", 1L); - mongoTemplate.updateFirst(query, update, "article"); - - articleRepo.findById(1L) - .ifPresent(article -> log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article - .getVisits())); - } - - /** - * 测试分页排序查询 - */ - @Test - public void testQuery() { - Sort sort = Sort.by("thumbUp", "updateTime").descending(); - PageRequest pageRequest = PageRequest.of(0, 5, sort); - Page
    all = articleRepo.findAll(pageRequest); - log.info("【总页数】= {}", all.getTotalPages()); - log.info("【总条数】= {}", all.getTotalElements()); - log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent() - .stream() - .map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp() + "更新时间:" + article.getUpdateTime()) - .collect(Collectors.toList()))); - } - - /** - * 测试根据标题模糊查询 - */ - @Test - public void testFindByTitleLike() { - List
    articles = articleRepo.findByTitleLike("更新"); - log.info("【articles】= {}", JSONUtil.toJsonStr(articles)); - } - -} -``` - -## 参考 - -1. Spring Data MongoDB 官方文档:https://docs.spring.io/spring-data/mongodb/docs/2.1.2.RELEASE/reference/html/ -2. MongoDB 官方镜像地址:https://hub.docker.com/_/mongo -3. MongoDB 官方快速入门:https://docs.mongodb.com/manual/tutorial/getting-started/ -4. MongoDB 官方文档:https://docs.mongodb.com/manual/ \ No newline at end of file diff --git a/spring-boot-demo-mongodb/pom.xml b/spring-boot-demo-mongodb/pom.xml deleted file mode 100644 index c88b49f..0000000 --- a/spring-boot-demo-mongodb/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-mongodb - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mongodb - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-mongodb - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java b/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java deleted file mode 100644 index bf63ed5..0000000 --- a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/SpringBootDemoMongodbApplication.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.mongodb; - -import cn.hutool.core.lang.Snowflake; -import cn.hutool.core.util.IdUtil; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.mongodb - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:14 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoMongodbApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMongodbApplication.class, args); - } - - @Bean - public Snowflake snowflake() { - return IdUtil.createSnowflake(1, 1); - } - -} - diff --git a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java b/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java deleted file mode 100644 index 0922ce5..0000000 --- a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/model/Article.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.mongodb.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; - -import java.util.Date; - -/** - *

    - * 文章实体类 - *

    - * - * @package: com.xkcoding.mongodb.model - * @description: 文章实体类 - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:21 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Article { - /** - * 文章id - */ - @Id - private Long id; - - /** - * 文章标题 - */ - private String title; - - /** - * 文章内容 - */ - private String content; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 更新时间 - */ - private Date updateTime; - - /** - * 点赞数量 - */ - private Long thumbUp; - - /** - * 访客数量 - */ - private Long visits; - -} diff --git a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java b/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java deleted file mode 100644 index a0ca60c..0000000 --- a/spring-boot-demo-mongodb/src/main/java/com/xkcoding/mongodb/repository/ArticleRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.mongodb.repository; - -import com.xkcoding.mongodb.model.Article; -import org.springframework.data.mongodb.repository.MongoRepository; - -import java.util.List; - -/** - *

    - * 文章 Dao - *

    - * - * @package: com.xkcoding.mongodb.repository - * @description: 文章 Dao - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface ArticleRepository extends MongoRepository { - /** - * 根据标题模糊查询 - * - * @param title 标题 - * @return 满足条件的文章列表 - */ - List
    findByTitleLike(String title); -} diff --git a/spring-boot-demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java b/spring-boot-demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java deleted file mode 100644 index e1c778e..0000000 --- a/spring-boot-demo-mongodb/src/test/java/com/xkcoding/mongodb/repository/ArticleRepositoryTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.xkcoding.mongodb.repository; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Snowflake; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.json.JSONUtil; -import com.google.common.collect.Lists; -import com.xkcoding.mongodb.SpringBootDemoMongodbApplicationTests; -import com.xkcoding.mongodb.model.Article; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * 测试操作 MongoDb - *

    - * - * @package: com.xkcoding.mongodb.repository - * @description: 测试操作 MongoDb - * @author: yangkai.shen - * @date: Created in 2018-12-28 16:35 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class ArticleRepositoryTest extends SpringBootDemoMongodbApplicationTests { - @Autowired - private ArticleRepository articleRepo; - - @Autowired - private MongoTemplate mongoTemplate; - - @Autowired - private Snowflake snowflake; - - /** - * 测试新增 - */ - @Test - public void testSave() { - Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil - .date(), 0L, 0L); - articleRepo.save(article); - log.info("【article】= {}", JSONUtil.toJsonStr(article)); - } - - /** - * 测试新增列表 - */ - @Test - public void testSaveList() { - List
    articles = Lists.newArrayList(); - for (int i = 0; i < 10; i++) { - articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil - .date(), DateUtil.date(), 0L, 0L)); - } - articleRepo.saveAll(articles); - - log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream() - .map(Article::getId) - .collect(Collectors.toList()))); - } - - /** - * 测试更新 - */ - @Test - public void testUpdate() { - articleRepo.findById(1L).ifPresent(article -> { - article.setTitle(article.getTitle() + "更新之后的标题"); - article.setUpdateTime(DateUtil.date()); - articleRepo.save(article); - log.info("【article】= {}", JSONUtil.toJsonStr(article)); - }); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - // 根据主键删除 - articleRepo.deleteById(1L); - - // 全部删除 - articleRepo.deleteAll(); - } - - /** - * 测试点赞数、访客数,使用save方式更新点赞、访客 - */ - @Test - public void testThumbUp() { - articleRepo.findById(1L).ifPresent(article -> { - article.setThumbUp(article.getThumbUp() + 1); - article.setVisits(article.getVisits() + 1); - articleRepo.save(article); - log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits()); - }); - } - - /** - * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客 - */ - @Test - public void testThumbUp2() { - Query query = new Query(); - query.addCriteria(Criteria.where("_id").is(1L)); - Update update = new Update(); - update.inc("thumbUp", 1L); - update.inc("visits", 1L); - mongoTemplate.updateFirst(query, update, "article"); - - articleRepo.findById(1L) - .ifPresent(article -> log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article - .getVisits())); - } - - /** - * 测试分页排序查询 - */ - @Test - public void testQuery() { - Sort sort = Sort.by("thumbUp", "updateTime").descending(); - PageRequest pageRequest = PageRequest.of(0, 5, sort); - Page
    all = articleRepo.findAll(pageRequest); - log.info("【总页数】= {}", all.getTotalPages()); - log.info("【总条数】= {}", all.getTotalElements()); - log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent() - .stream() - .map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp() + "更新时间:" + article.getUpdateTime()) - .collect(Collectors.toList()))); - } - - /** - * 测试根据标题模糊查询 - */ - @Test - public void testFindByTitleLike() { - List
    articles = articleRepo.findByTitleLike("更新"); - log.info("【articles】= {}", JSONUtil.toJsonStr(articles)); - } - -} \ No newline at end of file diff --git a/spring-boot-demo-mq-kafka/README.md b/spring-boot-demo-mq-kafka/README.md deleted file mode 100644 index 0c28892..0000000 --- a/spring-boot-demo-mq-kafka/README.md +++ /dev/null @@ -1,265 +0,0 @@ -# spring-boot-demo-mq-kafka - -> 本 demo 主要演示了 Spring Boot 如何集成 kafka,实现消息的发送和接收。 - -## 环境准备 - -> 注意:本 demo 基于 Spring Boot 2.1.0.RELEASE 版本,因此 spring-kafka 的版本为 2.2.0.RELEASE,kafka-clients 的版本为2.0.0,所以 kafka 的版本选用为 kafka_2.11-2.1.0 - -创建一个名为 `test` 的Topic - -```bash -./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test -``` - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-mq-kafka - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mq-kafka - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.kafka - spring-kafka - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-mq-kafka - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - kafka: - bootstrap-servers: localhost:9092 - producer: - retries: 0 - batch-size: 16384 - buffer-memory: 33554432 - key-serializer: org.apache.kafka.common.serialization.StringSerializer - value-serializer: org.apache.kafka.common.serialization.StringSerializer - consumer: - group-id: spring-boot-demo - # 手动提交 - enable-auto-commit: false - auto-offset-reset: latest - key-deserializer: org.apache.kafka.common.serialization.StringDeserializer - value-deserializer: org.apache.kafka.common.serialization.StringDeserializer - properties: - session.timeout.ms: 60000 - listener: - log-container-config: false - concurrency: 5 - # 手动提交 - ack-mode: manual_immediate -``` - -## KafkaConfig.java - -```java -/** - *

    - * kafka配置类 - *

    - * - * @package: com.xkcoding.mq.kafka.config - * @description: kafka配置类 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:49 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties({KafkaProperties.class}) -@EnableKafka -@AllArgsConstructor -public class KafkaConfig { - private final KafkaProperties kafkaProperties; - - @Bean - public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate<>(producerFactory()); - } - - @Bean - public ProducerFactory producerFactory() { - return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties()); - } - - @Bean - public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); - factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); - factory.setBatchListener(true); - factory.getContainerProperties().setPollTimeout(3000); - return factory; - } - - @Bean - public ConsumerFactory consumerFactory() { - return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); - } - - @Bean("ackContainerFactory") - public ConcurrentKafkaListenerContainerFactory ackContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); - factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); - factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); - return factory; - } - -} -``` - -## MessageHandler.java - -```java -/** - *

    - * 消息处理器 - *

    - * - * @package: com.xkcoding.mq.kafka.handler - * @description: 消息处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class MessageHandler { - - @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "ackContainerFactory") - public void handleMessage(ConsumerRecord record, Acknowledgment acknowledgment) { - try { - String message = (String) record.value(); - log.info("收到消息: {}", message); - } catch (Exception e) { - log.error(e.getMessage(), e); - } finally { - // 手动提交 offset - acknowledgment.acknowledge(); - } - } -} -``` - -## SpringBootDemoMqKafkaApplicationTests.java - -```java -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoMqKafkaApplicationTests { - @Autowired - private KafkaTemplate kafkaTemplate; - - /** - * 测试发送消息 - */ - @Test - public void testSend() { - kafkaTemplate.send(KafkaConsts.TOPIC_TEST, "hello,kafka..."); - } - -} -``` - -## 参考 - -1. Spring Boot 版本和 Spring-Kafka 的版本对应关系:https://spring.io/projects/spring-kafka - - | Spring for Apache Kafka Version | Spring Integration for Apache Kafka Version | kafka-clients | - | ------------------------------- | ------------------------------------------- | ------------------- | - | 2.2.x | 3.1.x | 2.0.0, 2.1.0 | - | 2.1.x | 3.0.x | 1.0.x, 1.1.x, 2.0.0 | - | 2.0.x | 3.0.x | 0.11.0.x, 1.0.x | - | 1.3.x | 2.3.x | 0.11.0.x, 1.0.x | - | 1.2.x | 2.2.x | 0.10.2.x | - | 1.1.x | 2.1.x | 0.10.0.x, 0.10.1.x | - | 1.0.x | 2.0.x | 0.9.x.x | - | N/A* | 1.3.x | 0.8.2.2 | - - > **IMPORTANT:** This matrix is client compatibility; in most cases (since 0.10.2.0) newer clients can communicate with older brokers. All users with brokers >= 0.10.x.x **(and all spring boot 1.5.x users)** are recommended to use spring-kafka version 1.3.x or higher due to its simpler threading model thanks to [KIP-62](https://cwiki.apache.org/confluence/display/KAFKA/KIP-62%3A+Allow+consumer+to+send+heartbeats+from+a+background+thread). For a complete discussion about client/broker compatibility, see the Kafka [Compatibility Matrix](https://cwiki.apache.org/confluence/display/KAFKA/Compatibility+Matrix) - > - > - Spring Integration Kafka versions prior to 2.0 pre-dated the Spring for Apache Kafka project and therefore were not based on it. - > - > These versions will be referenced transitively when using maven or gradle for version management. For the 1.1.x version, the 0.10.1.x is the default. - > - > 2.1.x uses the 1.1.x kafka-clients by default. When overriding the kafka-clients for 2.1.x see [the documentation appendix](https://docs.spring.io/spring-kafka/docs/2.1.x/reference/html/deps-for-11x.html). - > - > 2.2.x uses the 2.0.x kafka-clients by default. When overriding the kafka-clients for 2.2.x see [the documentation appendix](https://docs.spring.io/spring-kafka/docs/2.2.1.BUILD-SNAPSHOT/reference/html/deps-for-21x.html). - > - > - Spring Boot 1.5 users should use 1.3.x (Boot dependency management will use 1.1.x by default so this should be overridden). - > - Spring Boot 2.0 users should use 2.0.x (Boot dependency management will use the correct version). - > - Spring Boot 2.1 users should use 2.2.x (Boot dependency management will use the correct version). - -2. Spring-Kafka 官方文档:https://docs.spring.io/spring-kafka/docs/2.2.0.RELEASE/reference/html/ diff --git a/spring-boot-demo-mq-kafka/pom.xml b/spring-boot-demo-mq-kafka/pom.xml deleted file mode 100644 index 6e0cb55..0000000 --- a/spring-boot-demo-mq-kafka/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-mq-kafka - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mq-kafka - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.kafka - spring-kafka - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-mq-kafka - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java b/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java deleted file mode 100644 index cbbee84..0000000 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.mq.kafka; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.mq.kafka - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:43 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoMqKafkaApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMqKafkaApplication.class, args); - } - -} - diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java b/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java deleted file mode 100644 index 730cb07..0000000 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.mq.kafka.config; - -import com.xkcoding.mq.kafka.constants.KafkaConsts; -import lombok.AllArgsConstructor; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.annotation.EnableKafka; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.core.*; -import org.springframework.kafka.listener.ContainerProperties; - -/** - *

    - * kafka配置类 - *

    - * - * @package: com.xkcoding.mq.kafka.config - * @description: kafka配置类 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:49 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties({KafkaProperties.class}) -@EnableKafka -@AllArgsConstructor -public class KafkaConfig { - private final KafkaProperties kafkaProperties; - - @Bean - public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate<>(producerFactory()); - } - - @Bean - public ProducerFactory producerFactory() { - return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties()); - } - - @Bean - public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); - factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); - factory.setBatchListener(true); - factory.getContainerProperties().setPollTimeout(3000); - return factory; - } - - @Bean - public ConsumerFactory consumerFactory() { - return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); - } - - @Bean("ackContainerFactory") - public ConcurrentKafkaListenerContainerFactory ackContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); - factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); - factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM); - return factory; - } - -} diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java b/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java deleted file mode 100644 index 48518d7..0000000 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.mq.kafka.constants; - -/** - *

    - * kafka 常量池 - *

    - * - * @package: com.xkcoding.mq.kafka.constants - * @description: kafka 常量池 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:52 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface KafkaConsts { - /** - * 默认分区大小 - */ - Integer DEFAULT_PARTITION_NUM = 3; - - /** - * Topic 名称 - */ - String TOPIC_TEST = "test"; -} diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java b/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java deleted file mode 100644 index a55552e..0000000 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.mq.kafka.handler; - -import com.xkcoding.mq.kafka.constants.KafkaConsts; -import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.kafka.support.Acknowledgment; -import org.springframework.stereotype.Component; - -/** - *

    - * 消息处理器 - *

    - * - * @package: com.xkcoding.mq.kafka.handler - * @description: 消息处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class MessageHandler { - - @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "ackContainerFactory") - public void handleMessage(ConsumerRecord record, Acknowledgment acknowledgment) { - try { - String message = (String) record.value(); - log.info("收到消息: {}", message); - } catch (Exception e) { - log.error(e.getMessage(), e); - } finally { - // 手动提交 offset - acknowledgment.acknowledge(); - } - } -} diff --git a/spring-boot-demo-mq-rabbitmq/README.md b/spring-boot-demo-mq-rabbitmq/README.md deleted file mode 100644 index a18da05..0000000 --- a/spring-boot-demo-mq-rabbitmq/README.md +++ /dev/null @@ -1,549 +0,0 @@ -# spring-boot-demo-mq-rabbitmq - -> 此 demo 主要演示了 Spring Boot 如何集成 RabbitMQ,并且演示了基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收。 - -## 注意 - -作者编写本demo时,RabbitMQ 版本使用 `3.7.7-management`,使用 docker 运行,下面是所有步骤: - -1. 下载镜像:`docker pull rabbitmq:3.7.7-management` - -2. 运行容器:`docker run -d -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 --name rabbit-3.7.7 rabbitmq:3.7.7-management` - -3. 进入容器:`docker exec -it rabbit-3.7.7 /bin/bash` - -4. 给容器安装 下载工具 wget:`apt-get install -y wget` - -5. 下载插件包,因为我们的 `RabbitMQ` 版本为 `3.7.7` 所以我们安装 `3.7.x` 版本的延迟队列插件 - - ```bash - root@f72ac937f2be:/plugins# wget https://dl.bintray.com/rabbitmq/community-plugins/3.7.x/rabbitmq_delayed_message_exchange/rabbitmq_delayed_message_exchange-20171201-3.7.x.zip - ``` - -6. 给容器安装 解压工具 unzip:`apt-get install -y unzip` - -7. 解压插件包 - - ```bash - root@f72ac937f2be:/plugins# unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip - Archive: rabbitmq_delayed_message_exchange-20171201-3.7.x.zip - inflating: rabbitmq_delayed_message_exchange-20171201-3.7.x.ez - ``` - -8. 启动延迟队列插件 - - ```yaml - root@f72ac937f2be:/plugins# rabbitmq-plugins enable rabbitmq_delayed_message_exchange - The following plugins have been configured: - rabbitmq_delayed_message_exchange - rabbitmq_management - rabbitmq_management_agent - rabbitmq_web_dispatch - Applying plugin configuration to rabbit@f72ac937f2be... - The following plugins have been enabled: - rabbitmq_delayed_message_exchange - - started 1 plugins. - ``` - -9. 退出容器:`exit` - -10. 停止容器:`docker stop rabbit-3.7.7` - -11. 启动容器:`docker start rabbit-3.7.7` - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-mq-rabbitmq - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mq-rabbitmq - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-amqp - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-mq-rabbitmq - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - rabbitmq: - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / - # 手动提交消息 - listener: - simple: - acknowledge-mode: manual - direct: - acknowledge-mode: manual -``` - -## RabbitConsts.java - -```java -/** - *

    - * RabbitMQ常量池 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.constants - * @description: RabbitMQ常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:08 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface RabbitConsts { - /** - * 直接模式1 - */ - String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; - - /** - * 队列2 - */ - String QUEUE_TWO = "queue.2"; - - /** - * 队列3 - */ - String QUEUE_THREE = "3.queue"; - - /** - * 分列模式 - */ - String FANOUT_MODE_QUEUE = "fanout.mode"; - - /** - * 主题模式 - */ - String TOPIC_MODE_QUEUE = "topic.mode"; - - /** - * 路由1 - */ - String TOPIC_ROUTING_KEY_ONE = "queue.#"; - - /** - * 路由2 - */ - String TOPIC_ROUTING_KEY_TWO = "*.queue"; - - /** - * 路由3 - */ - String TOPIC_ROUTING_KEY_THREE = "3.queue"; - - /** - * 延迟队列 - */ - String DELAY_QUEUE = "delay.queue"; - - /** - * 延迟队列交换器 - */ - String DELAY_MODE_QUEUE = "delay.mode"; -} -``` - -## RabbitMqConfig.java - -> RoutingKey规则 -> -> - 路由格式必须以 `.` 分隔,比如 `user.email` 或者 `user.aaa.email` -> - 通配符 `*` ,代表一个占位符,或者说一个单词,比如路由为 `user.*`,那么 **`user.email`** 可以匹配,但是 *`user.aaa.email`* 就匹配不了 -> - 通配符 `#` ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 `user.#`,那么 **`user.email`** 可以匹配,**`user.aaa.email `** 也可以匹配 - -```java -/** - *

    - * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.config - * @description: RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Configuration -public class RabbitMqConfig { - - @Bean - public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { - connectionFactory.setPublisherConfirms(true); - connectionFactory.setPublisherReturns(true); - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); - rabbitTemplate.setMandatory(true); - rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); - rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message)); - return rabbitTemplate; - } - - /** - * 直接模式队列1 - */ - @Bean - public Queue directOneQueue() { - return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE); - } - - /** - * 队列2 - */ - @Bean - public Queue queueTwo() { - return new Queue(RabbitConsts.QUEUE_TWO); - } - - /** - * 队列3 - */ - @Bean - public Queue queueThree() { - return new Queue(RabbitConsts.QUEUE_THREE); - } - - /** - * 分列模式队列 - */ - @Bean - public FanoutExchange fanoutExchange() { - return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE); - } - - /** - * 分列模式绑定队列1 - * - * @param directOneQueue 绑定队列1 - * @param fanoutExchange 分列模式交换器 - */ - @Bean - public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(directOneQueue).to(fanoutExchange); - } - - /** - * 分列模式绑定队列2 - * - * @param queueTwo 绑定队列2 - * @param fanoutExchange 分列模式交换器 - */ - @Bean - public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(queueTwo).to(fanoutExchange); - } - - /** - * 主题模式队列 - *
  • 路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email
  • - *
  • 通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了
  • - *
  • 通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配
  • - */ - @Bean - public TopicExchange topicExchange() { - return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE); - } - - - /** - * 主题模式绑定分列模式 - * - * @param fanoutExchange 分列模式交换器 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) { - return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE); - } - - /** - * 主题模式绑定队列2 - * - * @param queueTwo 队列2 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) { - return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO); - } - - /** - * 主题模式绑定队列3 - * - * @param queueThree 队列3 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) { - return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE); - } - - /** - * 延迟队列 - */ - @Bean - public Queue delayQueue() { - return new Queue(RabbitConsts.DELAY_QUEUE, true); - } - - /** - * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定 - */ - @Bean - public CustomExchange delayExchange() { - Map args = Maps.newHashMap(); - args.put("x-delayed-type", "direct"); - return new CustomExchange(RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args); - } - - /** - * 延迟队列绑定自定义交换器 - * - * @param delayQueue 队列 - * @param delayExchange 延迟交换器 - */ - @Bean - public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) { - return BindingBuilder.bind(delayQueue).to(delayExchange).with(RabbitConsts.DELAY_QUEUE).noargs(); - } - -} -``` - -## 消息处理器 - -> 只展示直接队列模式的消息处理,其余模式请看源码 -> -> 需要注意:如果 `spring.rabbitmq.listener.direct.acknowledge-mode: auto`,则会自动Ack,否则需要手动Ack - -### DirectQueueOneHandler.java - -```java -/** - *

    - * 直接队列1 处理器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 直接队列1 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) -@Component -public class DirectQueueOneHandler { - - /** - * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack - */ - // @RabbitHandler - public void directHandlerAutoAck(MessageStruct message) { - log.info("直接队列处理器,接收消息:{}", JSONUtil.toJsonStr(message)); - } - - @RabbitHandler - public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { - // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 - final long deliveryTag = message.getMessageProperties().getDeliveryTag(); - try { - log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); - // 通知 MQ 消息已被成功消费,可以ACK了 - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - try { - // 处理失败,重新压入MQ - channel.basicRecover(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } -} -``` - -## SpringBootDemoMqRabbitmqApplicationTests.java - -```java -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoMqRabbitmqApplicationTests { - @Autowired - private RabbitTemplate rabbitTemplate; - - /** - * 测试直接模式发送 - */ - @Test - public void sendDirect() { - rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message")); - } - - /** - * 测试分列模式发送 - */ - @Test - public void sendFanout() { - rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message")); - } - - /** - * 测试主题模式发送1 - */ - @Test - public void sendTopic1() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message")); - } - - /** - * 测试主题模式发送2 - */ - @Test - public void sendTopic2() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message")); - } - - /** - * 测试主题模式发送3 - */ - @Test - public void sendTopic3() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message")); - } - - /** - * 测试延迟队列发送 - */ - @Test - public void sendDelay() { - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 5000); - return message; - }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 2000); - return message; - }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 8000); - return message; - }); - } - -} -``` - -## 运行效果 - -### 直接模式 - -![image-20190107103229408](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063315-1.jpg) - -### 分列模式 - -![image-20190107103258291](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063315.jpg) - -### 主题模式 - -#### RoutingKey:`queue.#` - -![image-20190107103358744](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063316.jpg) - -#### RoutingKey:`*.queue` - -![image-20190107103429430](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063312.jpg) - -#### RoutingKey:`3.queue` - -![image-20190107103451240](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063313.jpg) - -### 延迟队列 - -![image-20190107103509943](http://static.xkcoding.com/spring-boot-demo/mq/rabbitmq/063314.jpg) - -## 参考 - -1. Spring AMQP 官方文档:https://docs.spring.io/spring-amqp/docs/2.1.0.RELEASE/reference/html/ -2. RabbitMQ 官网:http://www.rabbitmq.com/ -3. RabbitMQ延迟队列:https://www.cnblogs.com/vipstone/p/9967649.html \ No newline at end of file diff --git a/spring-boot-demo-mq-rabbitmq/pom.xml b/spring-boot-demo-mq-rabbitmq/pom.xml deleted file mode 100644 index f442870..0000000 --- a/spring-boot-demo-mq-rabbitmq/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-mq-rabbitmq - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mq-rabbitmq - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-amqp - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-mq-rabbitmq - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java deleted file mode 100644 index e5c53d2..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.mq.rabbitmq; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-29 13:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoMqRabbitmqApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMqRabbitmqApplication.class, args); - } - -} - diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java deleted file mode 100644 index e55f01c..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.xkcoding.mq.rabbitmq.config; - -import com.google.common.collect.Maps; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.*; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -/** - *

    - * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.config - * @description: RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Configuration -public class RabbitMqConfig { - - @Bean - public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { - connectionFactory.setPublisherConfirms(true); - connectionFactory.setPublisherReturns(true); - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); - rabbitTemplate.setMandatory(true); - rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); - rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message)); - return rabbitTemplate; - } - - /** - * 直接模式队列1 - */ - @Bean - public Queue directOneQueue() { - return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE); - } - - /** - * 队列2 - */ - @Bean - public Queue queueTwo() { - return new Queue(RabbitConsts.QUEUE_TWO); - } - - /** - * 队列3 - */ - @Bean - public Queue queueThree() { - return new Queue(RabbitConsts.QUEUE_THREE); - } - - /** - * 分列模式队列 - */ - @Bean - public FanoutExchange fanoutExchange() { - return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE); - } - - /** - * 分列模式绑定队列1 - * - * @param directOneQueue 绑定队列1 - * @param fanoutExchange 分列模式交换器 - */ - @Bean - public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(directOneQueue).to(fanoutExchange); - } - - /** - * 分列模式绑定队列2 - * - * @param queueTwo 绑定队列2 - * @param fanoutExchange 分列模式交换器 - */ - @Bean - public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) { - return BindingBuilder.bind(queueTwo).to(fanoutExchange); - } - - /** - * 主题模式队列 - *
  • 路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email
  • - *
  • 通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了
  • - *
  • 通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配
  • - */ - @Bean - public TopicExchange topicExchange() { - return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE); - } - - - /** - * 主题模式绑定分列模式 - * - * @param fanoutExchange 分列模式交换器 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) { - return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE); - } - - /** - * 主题模式绑定队列2 - * - * @param queueTwo 队列2 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) { - return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO); - } - - /** - * 主题模式绑定队列3 - * - * @param queueThree 队列3 - * @param topicExchange 主题模式交换器 - */ - @Bean - public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) { - return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE); - } - - /** - * 延迟队列 - */ - @Bean - public Queue delayQueue() { - return new Queue(RabbitConsts.DELAY_QUEUE, true); - } - - /** - * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定 - */ - @Bean - public CustomExchange delayExchange() { - Map args = Maps.newHashMap(); - args.put("x-delayed-type", "direct"); - return new CustomExchange(RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args); - } - - /** - * 延迟队列绑定自定义交换器 - * - * @param delayQueue 队列 - * @param delayExchange 延迟交换器 - */ - @Bean - public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) { - return BindingBuilder.bind(delayQueue).to(delayExchange).with(RabbitConsts.DELAY_QUEUE).noargs(); - } - -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java deleted file mode 100644 index 8c117aa..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.xkcoding.mq.rabbitmq.constants; - -/** - *

    - * RabbitMQ常量池 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.constants - * @description: RabbitMQ常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:08 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface RabbitConsts { - /** - * 直接模式1 - */ - String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; - - /** - * 队列2 - */ - String QUEUE_TWO = "queue.2"; - - /** - * 队列3 - */ - String QUEUE_THREE = "3.queue"; - - /** - * 分列模式 - */ - String FANOUT_MODE_QUEUE = "fanout.mode"; - - /** - * 主题模式 - */ - String TOPIC_MODE_QUEUE = "topic.mode"; - - /** - * 路由1 - */ - String TOPIC_ROUTING_KEY_ONE = "queue.#"; - - /** - * 路由2 - */ - String TOPIC_ROUTING_KEY_TWO = "*.queue"; - - /** - * 路由3 - */ - String TOPIC_ROUTING_KEY_THREE = "3.queue"; - - /** - * 延迟队列 - */ - String DELAY_QUEUE = "delay.queue"; - - /** - * 延迟队列交换器 - */ - String DELAY_MODE_QUEUE = "delay.mode"; -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java deleted file mode 100644 index 6a07dd1..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.mq.rabbitmq.handler; - -import cn.hutool.json.JSONUtil; -import com.rabbitmq.client.Channel; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import com.xkcoding.mq.rabbitmq.message.MessageStruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -/** - *

    - * 延迟队列处理器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 延迟队列处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 17:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Component -@RabbitListener(queues = RabbitConsts.DELAY_QUEUE) -public class DelayQueueHandler { - - @RabbitHandler - public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { - // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 - final long deliveryTag = message.getMessageProperties().getDeliveryTag(); - try { - log.info("延迟队列,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); - // 通知 MQ 消息已被成功消费,可以ACK了 - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - try { - // 处理失败,重新压入MQ - channel.basicRecover(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java deleted file mode 100644 index 6ce77c8..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.xkcoding.mq.rabbitmq.handler; - -import cn.hutool.json.JSONUtil; -import com.rabbitmq.client.Channel; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import com.xkcoding.mq.rabbitmq.message.MessageStruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -/** - *

    - * 直接队列1 处理器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 直接队列1 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) -@Component -public class DirectQueueOneHandler { - - /** - * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack - */ - // @RabbitHandler - public void directHandlerAutoAck(MessageStruct message) { - log.info("直接队列处理器,接收消息:{}", JSONUtil.toJsonStr(message)); - } - - @RabbitHandler - public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { - // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 - final long deliveryTag = message.getMessageProperties().getDeliveryTag(); - try { - log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); - // 通知 MQ 消息已被成功消费,可以ACK了 - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - try { - // 处理失败,重新压入MQ - channel.basicRecover(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java deleted file mode 100644 index 79c284d..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.mq.rabbitmq.handler; - -import cn.hutool.json.JSONUtil; -import com.rabbitmq.client.Channel; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import com.xkcoding.mq.rabbitmq.message.MessageStruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -/** - *

    - * 队列2 处理器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 队列2 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RabbitListener(queues = RabbitConsts.QUEUE_THREE) -@Component -public class QueueThreeHandler { - - @RabbitHandler - public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { - // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 - final long deliveryTag = message.getMessageProperties().getDeliveryTag(); - try { - log.info("队列3,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); - // 通知 MQ 消息已被成功消费,可以ACK了 - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - try { - // 处理失败,重新压入MQ - channel.basicRecover(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java deleted file mode 100644 index 1a21743..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.mq.rabbitmq.handler; - -import cn.hutool.json.JSONUtil; -import com.rabbitmq.client.Channel; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import com.xkcoding.mq.rabbitmq.message.MessageStruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -/** - *

    - * 队列2 处理器 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 队列2 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RabbitListener(queues = RabbitConsts.QUEUE_TWO) -@Component -public class QueueTwoHandler { - - @RabbitHandler - public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { - // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 - final long deliveryTag = message.getMessageProperties().getDeliveryTag(); - try { - log.info("队列2,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); - // 通知 MQ 消息已被成功消费,可以ACK了 - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - try { - // 处理失败,重新压入MQ - channel.basicRecover(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } -} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java b/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java deleted file mode 100644 index 7d0553d..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.mq.rabbitmq.message; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 测试消息体 - *

    - * - * @package: com.xkcoding.mq.rabbitmq.message - * @description: 测试消息体 - * @author: yangkai.shen - * @date: Created in 2018-12-29 16:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MessageStruct implements Serializable { - private static final long serialVersionUID = 392365881428311040L; - - private String message; -} diff --git a/spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java b/spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java deleted file mode 100644 index 2a69a26..0000000 --- a/spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.xkcoding.mq.rabbitmq; - -import cn.hutool.core.date.DateUtil; -import com.xkcoding.mq.rabbitmq.constants.RabbitConsts; -import com.xkcoding.mq.rabbitmq.message.MessageStruct; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoMqRabbitmqApplicationTests { - @Autowired - private RabbitTemplate rabbitTemplate; - - /** - * 测试直接模式发送 - */ - @Test - public void sendDirect() { - rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message")); - } - - /** - * 测试分列模式发送 - */ - @Test - public void sendFanout() { - rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message")); - } - - /** - * 测试主题模式发送1 - */ - @Test - public void sendTopic1() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message")); - } - - /** - * 测试主题模式发送2 - */ - @Test - public void sendTopic2() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message")); - } - - /** - * 测试主题模式发送3 - */ - @Test - public void sendTopic3() { - rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message")); - } - - /** - * 测试延迟队列发送 - */ - @Test - public void sendDelay() { - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 5000); - return message; - }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 2000); - return message; - }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil - .date()), message -> { - message.getMessageProperties().setHeader("x-delay", 8000); - return message; - }); - } - -} - diff --git a/spring-boot-demo-mq-rocketmq/pom.xml b/spring-boot-demo-mq-rocketmq/pom.xml deleted file mode 100644 index 8f18be4..0000000 --- a/spring-boot-demo-mq-rocketmq/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-mq-rocketmq - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-mq-rocketmq - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-mq-rocketmq - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-multi-datasource-jpa/README.md b/spring-boot-demo-multi-datasource-jpa/README.md deleted file mode 100644 index 7f432a9..0000000 --- a/spring-boot-demo-multi-datasource-jpa/README.md +++ /dev/null @@ -1,556 +0,0 @@ -# spring-boot-demo-multi-datasource-jpa - -> 此 demo 主要演示 Spring Boot 如何集成 JPA 的多数据源。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-multi-datasource-jpa - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-multi-datasource-jpa - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-multi-datasource-jpa - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## PrimaryDataSourceConfig.java - -> 主数据源配置 - -```java -/** - *

    - * JPA多数据源配置 - 主数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class PrimaryDataSourceConfig { - - /** - * 扫描spring.datasource.primary开头的配置信息 - * - * @return 数据源配置信息 - */ - @Primary - @Bean(name = "primaryDataSourceProperties") - @ConfigurationProperties(prefix = "spring.datasource.primary") - public DataSourceProperties dataSourceProperties() { - return new DataSourceProperties(); - } - - /** - * 获取主库数据源对象 - * - * @param dataSourceProperties 注入名为primaryDataSourceProperties的bean - * @return 数据源对象 - */ - @Primary - @Bean(name = "primaryDataSource") - public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) { - return dataSourceProperties.initializeDataSourceBuilder().build(); - } - - /** - * 该方法仅在需要使用JdbcTemplate对象时选用 - * - * @param dataSource 注入名为primaryDataSource的bean - * @return 数据源JdbcTemplate对象 - */ - @Primary - @Bean(name = "primaryJdbcTemplate") - public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - -} -``` - -## SecondDataSourceConfig.java - -> 从数据源配置 - -```java -/** - *

    - * JPA多数据源配置 - 次数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class SecondDataSourceConfig { - - /** - * 扫描spring.datasource.second开头的配置信息 - * - * @return 数据源配置信息 - */ - @Bean(name = "secondDataSourceProperties") - @ConfigurationProperties(prefix = "spring.datasource.second") - public DataSourceProperties dataSourceProperties() { - return new DataSourceProperties(); - } - - /** - * 获取主库数据源对象 - * - * @param dataSourceProperties 注入名为secondDataSourceProperties的bean - * @return 数据源对象 - */ - @Bean(name = "secondDataSource") - public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) { - return dataSourceProperties.initializeDataSourceBuilder().build(); - } - - /** - * 该方法仅在需要使用JdbcTemplate对象时选用 - * - * @param dataSource 注入名为secondDataSource的bean - * @return 数据源JdbcTemplate对象 - */ - @Bean(name = "secondJdbcTemplate") - public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - -} -``` - -## PrimaryJpaConfig.java - -> 主 JPA 配置 - -```java -/** - *

    - * JPA多数据源配置 - 主 JPA 配置 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaRepositories( - // repository包名 - basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "primaryEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "primaryTransactionManager") -public class PrimaryJpaConfig { - static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; - private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; - - - /** - * 扫描spring.jpa.primary开头的配置信息 - * - * @return jpa配置信息 - */ - @Primary - @Bean(name = "primaryJpaProperties") - @ConfigurationProperties(prefix = "spring.jpa.primary") - public JpaProperties jpaProperties() { - return new JpaProperties(); - } - - /** - * 获取主库实体管理工厂对象 - * - * @param primaryDataSource 注入名为primaryDataSource的数据源 - * @param jpaProperties 注入名为primaryJpaProperties的jpa配置信息 - * @param builder 注入EntityManagerFactoryBuilder - * @return 实体管理工厂对象 - */ - @Primary - @Bean(name = "primaryEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { - return builder - // 设置数据源 - .dataSource(primaryDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("primaryPersistenceUnit").build(); - } - - /** - * 获取实体管理对象 - * - * @param factory 注入名为primaryEntityManagerFactory的bean - * @return 实体管理对象 - */ - @Primary - @Bean(name = "primaryEntityManager") - public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { - return factory.createEntityManager(); - } - - /** - * 获取主库事务管理对象 - * - * @param factory 注入名为primaryEntityManagerFactory的bean - * @return 事务管理对象 - */ - @Primary - @Bean(name = "primaryTransactionManager") - public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { - return new JpaTransactionManager(factory); - } - -} -``` - -## SecondJpaConfig.java - -> 从 JPA 配置 - -```java -/** - *

    - * JPA多数据源配置 - 次 JPA 配置 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaRepositories( - // repository包名 - basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "secondEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "secondTransactionManager") -public class SecondJpaConfig { - static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; - private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; - - - /** - * 扫描spring.jpa.second开头的配置信息 - * - * @return jpa配置信息 - */ - @Bean(name = "secondJpaProperties") - @ConfigurationProperties(prefix = "spring.jpa.second") - public JpaProperties jpaProperties() { - return new JpaProperties(); - } - - /** - * 获取主库实体管理工厂对象 - * - * @param secondDataSource 注入名为secondDataSource的数据源 - * @param jpaProperties 注入名为secondJpaProperties的jpa配置信息 - * @param builder 注入EntityManagerFactoryBuilder - * @return 实体管理工厂对象 - */ - @Bean(name = "secondEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { - return builder - // 设置数据源 - .dataSource(secondDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("secondPersistenceUnit").build(); - } - - /** - * 获取实体管理对象 - * - * @param factory 注入名为secondEntityManagerFactory的bean - * @return 实体管理对象 - */ - @Bean(name = "secondEntityManager") - public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { - return factory.createEntityManager(); - } - - /** - * 获取主库事务管理对象 - * - * @param factory 注入名为secondEntityManagerFactory的bean - * @return 事务管理对象 - */ - @Bean(name = "secondTransactionManager") - public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { - return new JpaTransactionManager(factory); - } - -} -``` - -## application.yml - -```yaml -spring: - datasource: - primary: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: PrimaryHikariCP - max-lifetime: 60000 - connection-timeout: 30000 - second: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SecondHikariCP - max-lifetime: 60000 - connection-timeout: 30000 - jpa: - primary: - show-sql: true - generate-ddl: true - hibernate: - ddl-auto: update - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL57InnoDBDialect - open-in-view: true - second: - show-sql: true - generate-ddl: true - hibernate: - ddl-auto: update - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL57InnoDBDialect - open-in-view: true -logging: - level: - com.xkcoding: debug - org.hibernate.SQL: debug - org.hibernate.type: trace -``` - -## SpringBootDemoMultiDatasourceJpaApplicationTests.java - -```java -package com.xkcoding.multi.datasource.jpa; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.Snowflake; -import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; -import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; -import com.xkcoding.multi.datasource.jpa.repository.primary.PrimaryMultiTableRepository; -import com.xkcoding.multi.datasource.jpa.repository.second.SecondMultiTableRepository; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.List; - -@RunWith(SpringRunner.class) -@SpringBootTest -@Slf4j -public class SpringBootDemoMultiDatasourceJpaApplicationTests { - @Autowired - private PrimaryMultiTableRepository primaryRepo; - @Autowired - private SecondMultiTableRepository secondRepo; - @Autowired - private Snowflake snowflake; - - @Test - public void testInsert() { - PrimaryMultiTable primary = new PrimaryMultiTable(snowflake.nextId(),"测试名称-1"); - primaryRepo.save(primary); - - SecondMultiTable second = new SecondMultiTable(); - BeanUtil.copyProperties(primary, second); - secondRepo.save(second); - } - - @Test - public void testUpdate() { - primaryRepo.findAll().forEach(primary -> { - primary.setName("修改后的"+primary.getName()); - primaryRepo.save(primary); - - SecondMultiTable second = new SecondMultiTable(); - BeanUtil.copyProperties(primary, second); - secondRepo.save(second); - }); - } - - @Test - public void testDelete() { - primaryRepo.deleteAll(); - - secondRepo.deleteAll(); - } - - @Test - public void testSelect() { - List primary = primaryRepo.findAll(); - log.info("【primary】= {}", primary); - - List second = secondRepo.findAll(); - log.info("【second】= {}", second); - } - -} -``` - -## 目录结构 - -``` -. -├── README.md -├── pom.xml -├── spring-boot-demo-multi-datasource-jpa.iml -├── src -│   ├── main -│   │   ├── java -│   │   │   └── com.xkcoding.multi.datasource.jpa -│   │   │   ├── SpringBootDemoMultiDatasourceJpaApplication.java -│   │   │   ├── config -│   │   │   │   ├── PrimaryDataSourceConfig.java -│   │   │   │   ├── PrimaryJpaConfig.java -│   │   │   │   ├── SecondDataSourceConfig.java -│   │   │   │   ├── SecondJpaConfig.java -│   │   │   │   └── SnowflakeConfig.java -│   │   │   ├── entity -│   │   │   │   ├── primary -│   │   │   │   │   └── PrimaryMultiTable.java -│   │   │   │   └── second -│   │   │   │   └── SecondMultiTable.java -│   │   │   └── repository -│   │   │   ├── primary -│   │   │   │   └── PrimaryMultiTableRepository.java -│   │   │   └── second -│   │   │   └── SecondMultiTableRepository.java -│   │   └── resources -│   │   └── application.yml -│   └── test -│   └── java -│   └── com.xkcoding.multi.datasource.jpa -│   └── SpringBootDemoMultiDatasourceJpaApplicationTests.java -└── target -``` - -## 参考 - -1. https://www.jianshu.com/p/34730e595a8c -2. https://blog.csdn.net/anxpp/article/details/52274120 \ No newline at end of file diff --git a/spring-boot-demo-multi-datasource-jpa/pom.xml b/spring-boot-demo-multi-datasource-jpa/pom.xml deleted file mode 100644 index 0204b3f..0000000 --- a/spring-boot-demo-multi-datasource-jpa/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-multi-datasource-jpa - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-multi-datasource-jpa - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-multi-datasource-jpa - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java deleted file mode 100644 index b8e1f1f..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.multi.datasource.jpa; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-01-16 17:34 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoMultiDatasourceJpaApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMultiDatasourceJpaApplication.class, args); - } - -} - diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java deleted file mode 100644 index 3d3825e..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.config; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; - -/** - *

    - * JPA多数据源配置 - 主数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class PrimaryDataSourceConfig { - - /** - * 扫描spring.datasource.primary开头的配置信息 - * - * @return 数据源配置信息 - */ - @Primary - @Bean(name = "primaryDataSourceProperties") - @ConfigurationProperties(prefix = "spring.datasource.primary") - public DataSourceProperties dataSourceProperties() { - return new DataSourceProperties(); - } - - /** - * 获取主库数据源对象 - * - * @param dataSourceProperties 注入名为primaryDataSourceProperties的bean - * @return 数据源对象 - */ - @Primary - @Bean(name = "primaryDataSource") - public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) { - return dataSourceProperties.initializeDataSourceBuilder().build(); - } - - /** - * 该方法仅在需要使用JdbcTemplate对象时选用 - * - * @param dataSource 注入名为primaryDataSource的bean - * @return 数据源JdbcTemplate对象 - */ - @Primary - @Bean(name = "primaryJdbcTemplate") - public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java deleted file mode 100644 index ba9fd5d..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.config; - -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -/** - *

    - * JPA多数据源配置 - 主 JPA 配置 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaRepositories( - // repository包名 - basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "primaryEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "primaryTransactionManager") -public class PrimaryJpaConfig { - static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; - private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; - - - /** - * 扫描spring.jpa.primary开头的配置信息 - * - * @return jpa配置信息 - */ - @Primary - @Bean(name = "primaryJpaProperties") - @ConfigurationProperties(prefix = "spring.jpa.primary") - public JpaProperties jpaProperties() { - return new JpaProperties(); - } - - /** - * 获取主库实体管理工厂对象 - * - * @param primaryDataSource 注入名为primaryDataSource的数据源 - * @param jpaProperties 注入名为primaryJpaProperties的jpa配置信息 - * @param builder 注入EntityManagerFactoryBuilder - * @return 实体管理工厂对象 - */ - @Primary - @Bean(name = "primaryEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { - return builder - // 设置数据源 - .dataSource(primaryDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("primaryPersistenceUnit").build(); - } - - /** - * 获取实体管理对象 - * - * @param factory 注入名为primaryEntityManagerFactory的bean - * @return 实体管理对象 - */ - @Primary - @Bean(name = "primaryEntityManager") - public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { - return factory.createEntityManager(); - } - - /** - * 获取主库事务管理对象 - * - * @param factory 注入名为primaryEntityManagerFactory的bean - * @return 事务管理对象 - */ - @Primary - @Bean(name = "primaryTransactionManager") - public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { - return new JpaTransactionManager(factory); - } - -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java deleted file mode 100644 index 4a79ddd..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.config; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; - -/** - *

    - * JPA多数据源配置 - 次数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class SecondDataSourceConfig { - - /** - * 扫描spring.datasource.second开头的配置信息 - * - * @return 数据源配置信息 - */ - @Bean(name = "secondDataSourceProperties") - @ConfigurationProperties(prefix = "spring.datasource.second") - public DataSourceProperties dataSourceProperties() { - return new DataSourceProperties(); - } - - /** - * 获取主库数据源对象 - * - * @param dataSourceProperties 注入名为secondDataSourceProperties的bean - * @return 数据源对象 - */ - @Bean(name = "secondDataSource") - public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) { - return dataSourceProperties.initializeDataSourceBuilder().build(); - } - - /** - * 该方法仅在需要使用JdbcTemplate对象时选用 - * - * @param dataSource 注入名为secondDataSource的bean - * @return 数据源JdbcTemplate对象 - */ - @Bean(name = "secondJdbcTemplate") - public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java deleted file mode 100644 index 1a4f1fe..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.config; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -/** - *

    - * JPA多数据源配置 - 次 JPA 配置 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaRepositories( - // repository包名 - basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "secondEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "secondTransactionManager") -public class SecondJpaConfig { - static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; - private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; - - - /** - * 扫描spring.jpa.second开头的配置信息 - * - * @return jpa配置信息 - */ - @Bean(name = "secondJpaProperties") - @ConfigurationProperties(prefix = "spring.jpa.second") - public JpaProperties jpaProperties() { - return new JpaProperties(); - } - - /** - * 获取主库实体管理工厂对象 - * - * @param secondDataSource 注入名为secondDataSource的数据源 - * @param jpaProperties 注入名为secondJpaProperties的jpa配置信息 - * @param builder 注入EntityManagerFactoryBuilder - * @return 实体管理工厂对象 - */ - @Bean(name = "secondEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { - return builder - // 设置数据源 - .dataSource(secondDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("secondPersistenceUnit").build(); - } - - /** - * 获取实体管理对象 - * - * @param factory 注入名为secondEntityManagerFactory的bean - * @return 实体管理对象 - */ - @Bean(name = "secondEntityManager") - public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { - return factory.createEntityManager(); - } - - /** - * 获取主库事务管理对象 - * - * @param factory 注入名为secondEntityManagerFactory的bean - * @return 事务管理对象 - */ - @Bean(name = "secondTransactionManager") - public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { - return new JpaTransactionManager(factory); - } - -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java deleted file mode 100644 index c143ed0..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.config; - -import cn.hutool.core.lang.Snowflake; -import cn.hutool.core.util.IdUtil; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * 雪花算法生成器 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: 雪花算法生成器 - * @author: yangkai.shen - * @date: Created in 2019-01-18 15:50 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class SnowflakeConfig { - @Bean - public Snowflake snowflake(){ - return IdUtil.createSnowflake(1,1); - } -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java deleted file mode 100644 index 414db78..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.entity.primary; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.GenericGenerator; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -/** - *

    - * 多数据源测试表 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.entity.primary - * @description: 多数据源测试表 - * @author: yangkai.shen - * @date: Created in 2019-01-18 10:06 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "multi_table") -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class PrimaryMultiTable { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 名称 - */ - private String name; -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java deleted file mode 100644 index b9e152b..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.entity.second; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.GenericGenerator; - -import javax.persistence.*; - -/** - *

    - * 多数据源测试表 - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.entity.second - * @description: 多数据源测试表 - * @author: yangkai.shen - * @date: Created in 2019-01-18 10:06 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "multi_table") -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class SecondMultiTable { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 名称 - */ - private String name; -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java deleted file mode 100644 index 9c8fe05..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.repository.primary; - -import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - *

    - * 多数据源测试 repo - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.repository.primary - * @description: 多数据源测试 repo - * @author: yangkai.shen - * @date: Created in 2019-01-18 10:11 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Repository -public interface PrimaryMultiTableRepository extends JpaRepository { -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java b/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java deleted file mode 100644 index 5752f55..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/second/SecondMultiTableRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.multi.datasource.jpa.repository.second; - -import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - *

    - * 多数据源测试 repo - *

    - * - * @package: com.xkcoding.multi.datasource.jpa.repository.second - * @description: 多数据源测试 repo - * @author: yangkai.shen - * @date: Created in 2019-01-18 10:11 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Repository -public interface SecondMultiTableRepository extends JpaRepository { -} diff --git a/spring-boot-demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java b/spring-boot-demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java deleted file mode 100644 index dd33ac9..0000000 --- a/spring-boot-demo-multi-datasource-jpa/src/test/java/com/xkcoding/multi/datasource/jpa/SpringBootDemoMultiDatasourceJpaApplicationTests.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.multi.datasource.jpa; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.Snowflake; -import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; -import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; -import com.xkcoding.multi.datasource.jpa.repository.primary.PrimaryMultiTableRepository; -import com.xkcoding.multi.datasource.jpa.repository.second.SecondMultiTableRepository; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.List; - -@RunWith(SpringRunner.class) -@SpringBootTest -@Slf4j -public class SpringBootDemoMultiDatasourceJpaApplicationTests { - @Autowired - private PrimaryMultiTableRepository primaryRepo; - @Autowired - private SecondMultiTableRepository secondRepo; - @Autowired - private Snowflake snowflake; - - @Test - public void testInsert() { - PrimaryMultiTable primary = new PrimaryMultiTable(snowflake.nextId(),"测试名称-1"); - primaryRepo.save(primary); - - SecondMultiTable second = new SecondMultiTable(); - BeanUtil.copyProperties(primary, second); - secondRepo.save(second); - } - - @Test - public void testUpdate() { - primaryRepo.findAll().forEach(primary -> { - primary.setName("修改后的"+primary.getName()); - primaryRepo.save(primary); - - SecondMultiTable second = new SecondMultiTable(); - BeanUtil.copyProperties(primary, second); - secondRepo.save(second); - }); - } - - @Test - public void testDelete() { - primaryRepo.deleteAll(); - - secondRepo.deleteAll(); - } - - @Test - public void testSelect() { - List primary = primaryRepo.findAll(); - log.info("【primary】= {}", primary); - - List second = secondRepo.findAll(); - log.info("【second】= {}", second); - } - -} - diff --git a/spring-boot-demo-multi-datasource-mybatis/README.md b/spring-boot-demo-multi-datasource-mybatis/README.md deleted file mode 100644 index fdd7389..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/README.md +++ /dev/null @@ -1,382 +0,0 @@ -# spring-boot-demo-multi-datasource-mybatis - -> 此 demo 主要演示了 Spring Boot 如何集成 Mybatis 的多数据源。可以自己基于AOP实现多数据源,这里基于 Mybatis-Plus 提供的一个优雅的开源的解决方案来实现。 - -## 准备工作 - -准备两个数据源,分别执行如下建表语句 - -```mysql -DROP TABLE IF EXISTS `multi_user`; -CREATE TABLE `multi_user`( - `id` bigint(64) NOT NULL, - `name` varchar(50) DEFAULT NULL, - `age` int(30) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB - AUTO_INCREMENT = 1 - CHARACTER SET = utf8 - COLLATE = utf8_general_ci; -``` - -## 导入依赖 - -```xml - - - 4.0.0 - - spring-boot-demo-multi-datasource-mybatis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-multi-datasource-mybatis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - com.baomidou - dynamic-datasource-spring-boot-starter - 2.5.0 - - - - com.baomidou - mybatis-plus-boot-starter - 3.0.7.1 - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-multi-datasource-mybatis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## 准备实体类 - -`User.java` - -> 1. @Data / @NoArgsConstructor / @AllArgsConstructor / @Builder 都是 lombok 注解 -> 2. @TableName("multi_user") 是 Mybatis-Plus 注解,主要是当实体类名字和表名不满足 **驼峰和下划线互转** 的格式时,用于表示数据库表名 -> 3. @TableId(type = IdType.ID_WORKER) 是 Mybatis-Plus 注解,主要是指定主键类型,这里我使用的是 Mybatis-Plus 基于 twitter 提供的 雪花算法 - -```java -/** - *

    - * User实体类 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.model - * @description: User实体类 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:19 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@TableName("multi_user") -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class User implements Serializable { - private static final long serialVersionUID = -1923859222295750467L; - - /** - * 主键 - */ - @TableId(type = IdType.ID_WORKER) - private Long id; - - /** - * 姓名 - */ - private String name; - - /** - * 年龄 - */ - private Integer age; -} -``` - -## 数据访问层 - -`UserMapper.java` - -> 不需要建对应的xml,只需要继承 BaseMapper 就拥有了大部分单表操作的方法了。 - -```java -/** - *

    - * 数据访问层 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.mapper - * @description: 数据访问层 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:28 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserMapper extends BaseMapper { -} -``` - -## 数据服务层 - -### 接口 - -`UserService.java` - -```java -/** - *

    - * 数据服务层 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service - * @description: 数据服务层 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:31 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService extends IService { - - /** - * 添加 User - * - * @param user 用户 - */ - void addUser(User user); -} -``` - -### 实现 - -`UserServiceImpl.java` - -> 1. @DS: 注解在类上或方法上来切换数据源,方法上的@DS优先级大于类上的@DS -> 2. baseMapper: mapper 对象,即`UserMapper`,可获得CRUD功能 -> 3. 默认走从库: `@DS(value = "slave")`在类上,默认走从库,除非在方法在添加`@DS(value = "master")`才走主库 - -```java -/** - *

    - * 数据服务层 实现 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service.impl - * @description: 数据服务层 实现 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:37 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@DS("slave") -public class UserServiceImpl extends ServiceImpl implements UserService { - - /** - * 类上 {@code @DS("slave")} 代表默认从库,在方法上写 {@code @DS("master")} 代表默认主库 - * - * @param user 用户 - */ - @DS("master") - @Override - public void addUser(User user) { - baseMapper.insert(user); - } -} -``` - -## 启动类 - -`SpringBootDemoMultiDatasourceMybatisApplication.java` - -> 启动类上方需要使用@MapperScan扫描 mapper 类所在的包 - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:19 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") -public class SpringBootDemoMultiDatasourceMybatisApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); - } - -} -``` - -## 配置文件 - -`application.yml` - -```yaml -spring: - datasource: - dynamic: - datasource: - master: - username: root - password: root - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - driver-class-name: com.mysql.cj.jdbc.Driver - slave: - username: root - password: root - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - driver-class-name: com.mysql.cj.jdbc.Driver - mp-enabled: true -logging: - level: - com.xkcoding.multi.datasource.mybatis: debug -``` - -## 测试类 - -```java -/** - *

    - * 测试主从数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service.impl - * @description: 测试主从数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:45 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceImplTest extends SpringBootDemoMultiDatasourceMybatisApplicationTests { - @Autowired - private UserService userService; - - /** - * 主从库添加 - */ - @Test - public void addUser() { - User userMaster = User.builder().name("主库添加").age(20).build(); - userService.addUser(userMaster); - - User userSlave = User.builder().name("从库添加").age(20).build(); - userService.save(userSlave); - } - - /** - * 从库查询 - */ - @Test - public void testListUser() { - List list = userService.list(new QueryWrapper<>()); - log.info("【list】= {}", JSONUtil.toJsonStr(list)); - } -} -``` - -### 测试结果 - -主从数据源加载成功 - -```java -2019-01-21 14:55:41.096 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : master - Starting... -2019-01-21 14:55:41.307 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : master - Start completed. -2019-01-21 14:55:41.308 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : slave - Starting... -2019-01-21 14:55:41.312 INFO 7239 --- [ main] com.zaxxer.hikari.HikariDataSource : slave - Start completed. -2019-01-21 14:55:41.312 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 初始共加载 2 个数据源 -2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 动态数据源-加载 slave 成功 -2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 动态数据源-加载 master 成功 -2019-01-21 14:55:41.313 INFO 7239 --- [ main] c.b.d.d.DynamicRoutingDataSource : 当前的默认数据源是单数据源,数据源名为 master - _ _ |_ _ _|_. ___ _ | _ -| | |\/|_)(_| | |_\ |_)||_|_\ - / | - 3.0.7.1 -``` - -**主**库 **建议** 只执行 **INSERT** **UPDATE** **DELETE** 操作 - -![image-20190121153211509](http://static.xkcoding.com/spring-boot-demo/multi-datasource/mybatis/063506.jpg) - -**从**库 **建议** 只执行 **SELECT** 操作 - -![image-20190121152825859](http://static.xkcoding.com/spring-boot-demo/multi-datasource/mybatis/063505.jpg) - -> 生产环境需要搭建 **主从复制** - -## 参考 - -1. Mybatis-Plus 多数据源文档:https://mybatis.plus/guide/dynamic-datasource.html -2. Mybatis-Plus 多数据源集成官方 demo:https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter/tree/master/samples \ No newline at end of file diff --git a/spring-boot-demo-multi-datasource-mybatis/pom.xml b/spring-boot-demo-multi-datasource-mybatis/pom.xml deleted file mode 100644 index bea8f8b..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-multi-datasource-mybatis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-multi-datasource-mybatis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - com.baomidou - dynamic-datasource-spring-boot-starter - 2.5.0 - - - - com.baomidou - mybatis-plus-boot-starter - 3.1.0 - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-multi-datasource-mybatis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java b/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java deleted file mode 100644 index 5fbc29c..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/SpringBootDemoMultiDatasourceMybatisApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:19 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") -public class SpringBootDemoMultiDatasourceMybatisApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); - } - -} - diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java b/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java deleted file mode 100644 index 51a6dd5..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.xkcoding.multi.datasource.mybatis.model.User; - -/** - *

    - * 数据访问层 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.mapper - * @description: 数据访问层 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:28 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserMapper extends BaseMapper { -} diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java b/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java deleted file mode 100644 index f895d3c..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/model/User.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis.model; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * User实体类 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.model - * @description: User实体类 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:19 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@TableName("multi_user") -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class User implements Serializable { - private static final long serialVersionUID = -1923859222295750467L; - - /** - * 主键 - */ - @TableId(type = IdType.ID_WORKER) - private Long id; - - /** - * 姓名 - */ - private String name; - - /** - * 年龄 - */ - private Integer age; -} \ No newline at end of file diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java b/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java deleted file mode 100644 index f9d84fd..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import com.xkcoding.multi.datasource.mybatis.model.User; - -/** - *

    - * 数据服务层 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service - * @description: 数据服务层 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:31 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService extends IService { - - /** - * 添加 User - * - * @param user 用户 - */ - void addUser(User user); -} diff --git a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java b/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java deleted file mode 100644 index 2d4bec0..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis.service.impl; - -import com.baomidou.dynamic.datasource.annotation.DS; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.xkcoding.multi.datasource.mybatis.mapper.UserMapper; -import com.xkcoding.multi.datasource.mybatis.model.User; -import com.xkcoding.multi.datasource.mybatis.service.UserService; -import org.springframework.stereotype.Service; - -/** - *

    - * 数据服务层 实现 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service.impl - * @description: 数据服务层 实现 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:37 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@DS("slave") -public class UserServiceImpl extends ServiceImpl implements UserService { - - /** - * 类上 {@code @DS("slave")} 代表默认从库,在方法上写 {@code @DS("master")} 代表默认主库 - * - * @param user 用户 - */ - @DS("master") - @Override - public void addUser(User user) { - baseMapper.insert(user); - } -} diff --git a/spring-boot-demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java b/spring-boot-demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java deleted file mode 100644 index a57b732..0000000 --- a/spring-boot-demo-multi-datasource-mybatis/src/test/java/com/xkcoding/multi/datasource/mybatis/service/impl/UserServiceImplTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.xkcoding.multi.datasource.mybatis.service.impl; - -import cn.hutool.json.JSONUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.xkcoding.multi.datasource.mybatis.SpringBootDemoMultiDatasourceMybatisApplicationTests; -import com.xkcoding.multi.datasource.mybatis.model.User; -import com.xkcoding.multi.datasource.mybatis.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -/** - *

    - * 测试主从数据源 - *

    - * - * @package: com.xkcoding.multi.datasource.mybatis.service.impl - * @description: 测试主从数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-21 14:45 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceImplTest extends SpringBootDemoMultiDatasourceMybatisApplicationTests { - @Autowired - private UserService userService; - - /** - * 主从库添加 - */ - @Test - public void addUser() { - User userMaster = User.builder().name("主库添加").age(20).build(); - userService.addUser(userMaster); - - User userSlave = User.builder().name("从库添加").age(20).build(); - userService.save(userSlave); - } - - /** - * 从库查询 - */ - @Test - public void testListUser() { - List list = userService.list(new QueryWrapper<>()); - log.info("【list】= {}", JSONUtil.toJsonStr(list)); - } -} \ No newline at end of file diff --git a/spring-boot-demo-neo4j/README.md b/spring-boot-demo-neo4j/README.md deleted file mode 100644 index 08466e5..0000000 --- a/spring-boot-demo-neo4j/README.md +++ /dev/null @@ -1,339 +0,0 @@ -# spring-boot-demo-neo4j - -> 此 demo 主要演示了 Spring Boot 如何集成Neo4j操作图数据库,实现一个校园人物关系网。 - -## 注意 - -作者编写本demo时,Neo4j 版本为 `3.5.0`,使用 docker 运行,下面是所有步骤: - -1. 下载镜像:`docker pull neo4j:3.5.0` -2. 运行容器:`docker run -d -p 7474:7474 -p 7687:7687 --name neo4j-3.5.0 neo4j:3.5.0` -3. 停止容器:`docker stop neo4j-3.5.0` -4. 启动容器:`docker start neo4j-3.5.0` -5. 浏览器 http://localhost:7474/ 访问 neo4j 管理后台,初始账号/密码 neo4j/neo4j,会要求修改初始化密码,我们修改为 neo4j/admin - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-neo4j - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-neo4j - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-data-neo4j - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-neo4j - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -spring: - data: - neo4j: - uri: bolt://localhost - username: neo4j - password: admin - open-in-view: false -``` - -## CustomIdStrategy.java - -```java -/** - *

    - * 自定义主键策略 - *

    - * - * @package: com.xkcoding.neo4j.config - * @description: 自定义主键策略 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:40 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class CustomIdStrategy implements IdStrategy { - @Override - public Object generateId(Object o) { - return IdUtil.fastUUID(); - } -} -``` - -## 部分Model代码 - -### Student.java - -```java -/** - *

    - * 学生节点 - *

    - * - * @package: com.xkcoding.neo4j.model - * @description: 学生节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:38 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@RequiredArgsConstructor(staticName = "of") -@AllArgsConstructor -@Builder -@NodeEntity -public class Student { - /** - * 主键,自定义主键策略,使用UUID生成 - */ - @Id - @GeneratedValue(strategy = CustomIdStrategy.class) - private String id; - - /** - * 学生姓名 - */ - @NonNull - private String name; - - /** - * 学生选的所有课程 - */ - @Relationship(NeoConsts.R_LESSON_OF_STUDENT) - @NonNull - private List lessons; - - /** - * 学生所在班级 - */ - @Relationship(NeoConsts.R_STUDENT_OF_CLASS) - @NonNull - private Class clazz; - -} -``` - -## 部分Repository代码 - -### StudentRepository.java - -```java -/** - *

    - * 学生节点Repository - *

    - * - * @package: com.xkcoding.neo4j.repository - * @description: 学生节点Repository - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface StudentRepository extends Neo4jRepository { - /** - * 根据名称查找学生 - * - * @param name 姓名 - * @param depth 深度 - * @return 学生信息 - */ - Optional findByName(String name, @Depth int depth); - - /** - * 根据班级查询班级人数 - * - * @param className 班级名称 - * @return 班级人数 - */ - @Query("MATCH (s:Student)-[r:R_STUDENT_OF_CLASS]->(c:Class{name:{className}}) return count(s)") - Long countByClassName(@Param("className") String className); - - - /** - * 查询满足 (学生)-[选课关系]-(课程)-[选课关系]-(学生) 关系的 同学 - * - * @return 返回同学关系 - */ - @Query("match (s:Student)-[:R_LESSON_OF_STUDENT]->(l:Lesson)<-[:R_LESSON_OF_STUDENT]-(:Student) with l.name as lessonName,collect(distinct s) as students return lessonName,students") - List findByClassmateGroupByLesson(); - - /** - * 查询师生关系,(学生)-[班级学生关系]-(班级)-[班主任关系]-(教师) - * - * @return 返回师生关系 - */ - @Query("match (s:Student)-[:R_STUDENT_OF_CLASS]->(:Class)-[:R_BOSS_OF_CLASS]->(t:Teacher) with t.name as teacherName,collect(distinct s) as students return teacherName,students") - List findTeacherStudentByClass(); - - /** - * 查询师生关系,(学生)-[选课关系]-(课程)-[任教老师关系]-(教师) - * - * @return 返回师生关系 - */ - @Query("match ((s:Student)-[:R_LESSON_OF_STUDENT]->(:Lesson)-[:R_TEACHER_OF_LESSON]->(t:Teacher))with t.name as teacherName,collect(distinct s) as students return teacherName,students") - List findTeacherStudentByLesson(); -} -``` - -## Neo4jTest.java - -```java -/** - *

    - * 测试Neo4j - *

    - * - * @package: com.xkcoding.neo4j - * @description: 测试Neo4j - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { - @Autowired - private NeoService neoService; - - /** - * 测试保存 - */ - @Test - public void testSave() { - neoService.initData(); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - neoService.delete(); - } - - /** - * 测试查询 鸣人 学了哪些课程 - */ - @Test - public void testFindLessonsByStudent() { - // 深度为1,则课程的任教老师的属性为null - // 深度为2,则会把课程的任教老师的属性赋值 - List lessons = neoService.findLessonsFromStudent("漩涡鸣人", 2); - - lessons.forEach(lesson -> log.info("【lesson】= {}", JSONUtil.toJsonStr(lesson))); - } - - /** - * 测试查询班级人数 - */ - @Test - public void testCountStudent() { - Long all = neoService.studentCount(null); - log.info("【全校人数】= {}", all); - Long seven = neoService.studentCount("第七班"); - log.info("【第七班人数】= {}", seven); - } - - /** - * 测试根据课程查询同学关系 - */ - @Test - public void testFindClassmates() { - Map> classmates = neoService.findClassmatesGroupByLesson(); - classmates.forEach((k, v) -> log.info("因为一起上了【{}】这门课,成为同学关系的有:{}", k, JSONUtil.toJsonStr(v.stream() - .map(Student::getName) - .collect(Collectors.toList())))); - } - - /** - * 查询所有师生关系,包括班主任/学生,任课老师/学生 - */ - @Test - public void testFindTeacherStudent() { - Map> teacherStudent = neoService.findTeacherStudent(); - teacherStudent.forEach((k, v) -> log.info("【{}】教的学生有 {}", k, JSONUtil.toJsonStr(v.stream() - .map(Student::getName) - .collect(Collectors.toList())))); - } -} -``` - -## 截图 - -运行测试类之后,可以通过访问 http://localhost:7474 ,查看neo里所有节点和关系 - -![image-20181225150513101](http://static.xkcoding.com/spring-boot-demo/neo4j/063605.jpg) - - - -## 参考 - -- spring-data-neo4j 官方文档:https://docs.spring.io/spring-data/neo4j/docs/5.1.2.RELEASE/reference/html/ -- neo4j 官方文档:https://neo4j.com/docs/getting-started/3.5/ \ No newline at end of file diff --git a/spring-boot-demo-neo4j/pom.xml b/spring-boot-demo-neo4j/pom.xml deleted file mode 100644 index 0526dee..0000000 --- a/spring-boot-demo-neo4j/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-neo4j - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-neo4j - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-data-neo4j - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - - spring-boot-demo-neo4j - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java deleted file mode 100644 index b15c70d..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/SpringBootDemoNeo4jApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.neo4j; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.neo4j - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-22 23:50 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoNeo4jApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoNeo4jApplication.class, args); - } -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java deleted file mode 100644 index 5cc8778..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.neo4j.config; - -import cn.hutool.core.util.IdUtil; -import org.neo4j.ogm.id.IdStrategy; - -/** - *

    - * 自定义主键策略 - *

    - * - * @package: com.xkcoding.neo4j.config - * @description: 自定义主键策略 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:40 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class CustomIdStrategy implements IdStrategy { - @Override - public Object generateId(Object o) { - return IdUtil.fastUUID(); - } -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java deleted file mode 100644 index 0ea6f9d..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.neo4j.constants; - -/** - *

    - * 常量池 - *

    - * - * @package: com.xkcoding.neo4j.constants - * @description: 常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface NeoConsts { - /** - * 关系:班级拥有的学生 - */ - String R_STUDENT_OF_CLASS = "R_STUDENT_OF_CLASS"; - - /** - * 关系:班级的班主任 - */ - String R_BOSS_OF_CLASS = "R_BOSS_OF_CLASS"; - - /** - * 关系:课程的老师 - */ - String R_TEACHER_OF_LESSON = "R_TEACHER_OF_LESSON"; - - /** - * 关系:学生选的课 - */ - String R_LESSON_OF_STUDENT = "R_LESSON_OF_STUDENT"; - -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java deleted file mode 100644 index 8af1f66..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.neo4j.model; - -import com.xkcoding.neo4j.config.CustomIdStrategy; -import com.xkcoding.neo4j.constants.NeoConsts; -import lombok.*; -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; -import org.neo4j.ogm.annotation.Relationship; - -/** - *

    - * 班级节点 - *

    - * - * @package: com.xkcoding.neo4j.model - * @description: 班级节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:44 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@RequiredArgsConstructor(staticName = "of") -@AllArgsConstructor -@Builder -@NodeEntity -public class Class { - /** - * 主键 - */ - @Id - @GeneratedValue(strategy = CustomIdStrategy.class) - private String id; - - /** - * 班级名称 - */ - @NonNull - private String name; - - /** - * 班级的班主任 - */ - @Relationship(NeoConsts.R_BOSS_OF_CLASS) - @NonNull - private Teacher boss; -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java deleted file mode 100644 index 8d96d42..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xkcoding.neo4j.model; - -import com.xkcoding.neo4j.config.CustomIdStrategy; -import com.xkcoding.neo4j.constants.NeoConsts; -import lombok.*; -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; -import org.neo4j.ogm.annotation.Relationship; - -/** - *

    - * 课程节点 - *

    - * - * @package: com.xkcoding.neo4j.model - * @description: 课程节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@RequiredArgsConstructor(staticName = "of") -@AllArgsConstructor -@Builder -@NodeEntity -public class Lesson { - /** - * 主键,自定义主键策略,使用UUID生成 - */ - @Id - @GeneratedValue(strategy = CustomIdStrategy.class) - private String id; - - /** - * 课程名称 - */ - @NonNull - private String name; - - /** - * 任教老师 - */ - @Relationship(NeoConsts.R_TEACHER_OF_LESSON) - @NonNull - private Teacher teacher; -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java deleted file mode 100644 index 4ce6a85..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.xkcoding.neo4j.model; - -import com.xkcoding.neo4j.config.CustomIdStrategy; -import com.xkcoding.neo4j.constants.NeoConsts; -import lombok.*; -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; -import org.neo4j.ogm.annotation.Relationship; - -import java.util.List; - -/** - *

    - * 学生节点 - *

    - * - * @package: com.xkcoding.neo4j.model - * @description: 学生节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:38 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@RequiredArgsConstructor(staticName = "of") -@AllArgsConstructor -@Builder -@NodeEntity -public class Student { - /** - * 主键,自定义主键策略,使用UUID生成 - */ - @Id - @GeneratedValue(strategy = CustomIdStrategy.class) - private String id; - - /** - * 学生姓名 - */ - @NonNull - private String name; - - /** - * 学生选的所有课程 - */ - @Relationship(NeoConsts.R_LESSON_OF_STUDENT) - @NonNull - private List lessons; - - /** - * 学生所在班级 - */ - @Relationship(NeoConsts.R_STUDENT_OF_CLASS) - @NonNull - private Class clazz; - -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java deleted file mode 100644 index 968ddb1..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.neo4j.model; - -import com.xkcoding.neo4j.config.CustomIdStrategy; -import lombok.*; -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; - -/** - *

    - * 教师节点 - *

    - * - * @package: com.xkcoding.neo4j.model - * @description: 教师节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@RequiredArgsConstructor(staticName = "of") -@AllArgsConstructor -@Builder -@NodeEntity -public class Teacher { - /** - * 主键,自定义主键策略,使用UUID生成 - */ - @Id - @GeneratedValue(strategy = CustomIdStrategy.class) - private String id; - - /** - * 教师姓名 - */ - @NonNull - private String name; -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java deleted file mode 100644 index b5a156c..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.neo4j.payload; - -import com.xkcoding.neo4j.model.Student; -import lombok.Data; -import org.springframework.data.neo4j.annotation.QueryResult; - -import java.util.List; - -/** - *

    - * 按照课程分组的同学关系 - *

    - * - * @package: com.xkcoding.neo4j.payload - * @description: 按照课程分组的同学关系 - * @author: yangkai.shen - * @date: Created in 2018-12-24 19:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@QueryResult -public class ClassmateInfoGroupByLesson { - /** - * 课程名称 - */ - private String lessonName; - - /** - * 学生信息 - */ - private List students; -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java deleted file mode 100644 index 57eca84..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/TeacherStudent.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.neo4j.payload; - -import com.xkcoding.neo4j.model.Student; -import lombok.Data; -import org.springframework.data.neo4j.annotation.QueryResult; - -import java.util.List; - -/** - *

    - * 师生关系 - *

    - * - * @package: com.xkcoding.neo4j.payload - * @description: 师生关系 - * @author: yangkai.shen - * @date: Created in 2018-12-24 19:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@QueryResult -public class TeacherStudent { - /** - * 教师姓名 - */ - private String teacherName; - - /** - * 学生信息 - */ - private List students; -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java deleted file mode 100644 index e8c59b9..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/ClassRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.neo4j.repository; - -import com.xkcoding.neo4j.model.Class; -import org.springframework.data.neo4j.repository.Neo4jRepository; - -import java.util.Optional; - -/** - *

    - * 班级节点Repository - *

    - * - * @package: com.xkcoding.neo4j.repository - * @description: 班级节点Repository - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface ClassRepository extends Neo4jRepository { - /** - * 根据班级名称查询班级信息 - * - * @param name 班级名称 - * @return 班级信息 - */ - Optional findByName(String name); -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java deleted file mode 100644 index a4f7b9d..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/LessonRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding.neo4j.repository; - -import com.xkcoding.neo4j.model.Lesson; -import org.springframework.data.neo4j.repository.Neo4jRepository; - -/** - *

    - * 课程节点Repository - *

    - * - * @package: com.xkcoding.neo4j.repository - * @description: 课程节点Repository - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface LessonRepository extends Neo4jRepository { -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java deleted file mode 100644 index 00956d7..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/StudentRepository.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.xkcoding.neo4j.repository; - -import com.xkcoding.neo4j.model.Student; -import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; -import com.xkcoding.neo4j.payload.TeacherStudent; -import org.springframework.data.neo4j.annotation.Depth; -import org.springframework.data.neo4j.annotation.Query; -import org.springframework.data.neo4j.repository.Neo4jRepository; -import org.springframework.data.repository.query.Param; - -import java.util.List; -import java.util.Optional; - -/** - *

    - * 学生节点Repository - *

    - * - * @package: com.xkcoding.neo4j.repository - * @description: 学生节点Repository - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface StudentRepository extends Neo4jRepository { - /** - * 根据名称查找学生 - * - * @param name 姓名 - * @param depth 深度 - * @return 学生信息 - */ - Optional findByName(String name, @Depth int depth); - - /** - * 根据班级查询班级人数 - * - * @param className 班级名称 - * @return 班级人数 - */ - @Query("MATCH (s:Student)-[r:R_STUDENT_OF_CLASS]->(c:Class{name:{className}}) return count(s)") - Long countByClassName(@Param("className") String className); - - - /** - * 查询满足 (学生)-[选课关系]-(课程)-[选课关系]-(学生) 关系的 同学 - * - * @return 返回同学关系 - */ - @Query("match (s:Student)-[:R_LESSON_OF_STUDENT]->(l:Lesson)<-[:R_LESSON_OF_STUDENT]-(:Student) with l.name as lessonName,collect(distinct s) as students return lessonName,students") - List findByClassmateGroupByLesson(); - - /** - * 查询师生关系,(学生)-[班级学生关系]-(班级)-[班主任关系]-(教师) - * - * @return 返回师生关系 - */ - @Query("match (s:Student)-[:R_STUDENT_OF_CLASS]->(:Class)-[:R_BOSS_OF_CLASS]->(t:Teacher) with t.name as teacherName,collect(distinct s) as students return teacherName,students") - List findTeacherStudentByClass(); - - /** - * 查询师生关系,(学生)-[选课关系]-(课程)-[任教老师关系]-(教师) - * - * @return 返回师生关系 - */ - @Query("match ((s:Student)-[:R_LESSON_OF_STUDENT]->(:Lesson)-[:R_TEACHER_OF_LESSON]->(t:Teacher))with t.name as teacherName,collect(distinct s) as students return teacherName,students") - List findTeacherStudentByLesson(); -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java deleted file mode 100644 index 380f1ff..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/repository/TeacherRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding.neo4j.repository; - -import com.xkcoding.neo4j.model.Teacher; -import org.springframework.data.neo4j.repository.Neo4jRepository; - -/** - *

    - * 教师节点Repository - *

    - * - * @package: com.xkcoding.neo4j.repository - * @description: 教师节点Repository - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface TeacherRepository extends Neo4jRepository { -} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java b/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java deleted file mode 100644 index b0a04f3..0000000 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/service/NeoService.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.xkcoding.neo4j.service; - -import cn.hutool.core.util.StrUtil; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.xkcoding.neo4j.model.Class; -import com.xkcoding.neo4j.model.Lesson; -import com.xkcoding.neo4j.model.Student; -import com.xkcoding.neo4j.model.Teacher; -import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; -import com.xkcoding.neo4j.payload.TeacherStudent; -import com.xkcoding.neo4j.repository.ClassRepository; -import com.xkcoding.neo4j.repository.LessonRepository; -import com.xkcoding.neo4j.repository.StudentRepository; -import com.xkcoding.neo4j.repository.TeacherRepository; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; -import org.neo4j.ogm.transaction.Transaction; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - *

    - * NeoService - *

    - * - * @package: com.xkcoding.neo4j.service - * @description: NeoService - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:19 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class NeoService { - @Autowired - private ClassRepository classRepo; - - @Autowired - private LessonRepository lessonRepo; - - @Autowired - private StudentRepository studentRepo; - - @Autowired - private TeacherRepository teacherRepo; - - @Autowired - private SessionFactory sessionFactory; - - /** - * 初始化数据 - */ - @Transactional - public void initData() { - // 初始化老师 - Teacher akai = Teacher.of("迈特凯"); - Teacher kakaxi = Teacher.of("旗木卡卡西"); - Teacher zilaiye = Teacher.of("自来也"); - Teacher gangshou = Teacher.of("纲手"); - Teacher dashewan = Teacher.of("大蛇丸"); - teacherRepo.save(akai); - teacherRepo.save(kakaxi); - teacherRepo.save(zilaiye); - teacherRepo.save(gangshou); - teacherRepo.save(dashewan); - - // 初始化课程 - Lesson tishu = Lesson.of("体术", akai); - Lesson huanshu = Lesson.of("幻术", kakaxi); - Lesson shoulijian = Lesson.of("手里剑", kakaxi); - Lesson luoxuanwan = Lesson.of("螺旋丸", zilaiye); - Lesson xianshu = Lesson.of("仙术", zilaiye); - Lesson yiliao = Lesson.of("医疗", gangshou); - Lesson zhouyin = Lesson.of("咒印", dashewan); - lessonRepo.save(tishu); - lessonRepo.save(huanshu); - lessonRepo.save(shoulijian); - lessonRepo.save(luoxuanwan); - lessonRepo.save(xianshu); - lessonRepo.save(yiliao); - lessonRepo.save(zhouyin); - - // 初始化班级 - Class three = Class.of("第三班", akai); - Class seven = Class.of("第七班", kakaxi); - classRepo.save(three); - classRepo.save(seven); - - // 初始化学生 - List threeClass = Lists.newArrayList(Student.of("漩涡鸣人", Lists.newArrayList(tishu, shoulijian, luoxuanwan, xianshu), seven), Student - .of("宇智波佐助", Lists.newArrayList(huanshu, zhouyin, shoulijian), seven), Student.of("春野樱", Lists.newArrayList(tishu, yiliao, shoulijian), seven)); - List sevenClass = Lists.newArrayList(Student.of("李洛克", Lists.newArrayList(tishu), three), Student.of("日向宁次", Lists - .newArrayList(tishu), three), Student.of("天天", Lists.newArrayList(tishu), three)); - - studentRepo.saveAll(threeClass); - studentRepo.saveAll(sevenClass); - - } - - /** - * 删除数据 - */ - @Transactional - public void delete() { - // 使用语句删除 - Session session = sessionFactory.openSession(); - Transaction transaction = session.beginTransaction(); - session.query("match (n)-[r]-() delete n,r", Maps.newHashMap()); - session.query("match (n)-[r]-() delete r", Maps.newHashMap()); - session.query("match (n) delete n", Maps.newHashMap()); - transaction.commit(); - - // 使用 repository 删除 - studentRepo.deleteAll(); - classRepo.deleteAll(); - lessonRepo.deleteAll(); - teacherRepo.deleteAll(); - } - - /** - * 根据学生姓名查询所选课程 - * - * @param studentName 学生姓名 - * @param depth 深度 - * @return 课程列表 - */ - public List findLessonsFromStudent(String studentName, int depth) { - List lessons = Lists.newArrayList(); - studentRepo.findByName(studentName, depth).ifPresent(student -> lessons.addAll(student.getLessons())); - return lessons; - } - - /** - * 查询全校学生数 - * - * @return 学生总数 - */ - public Long studentCount(String className) { - if (StrUtil.isBlank(className)) { - return studentRepo.count(); - } else { - return studentRepo.countByClassName(className); - } - } - - /** - * 查询同学关系,根据课程 - * - * @return 返回同学关系 - */ - public Map> findClassmatesGroupByLesson() { - List groupByLesson = studentRepo.findByClassmateGroupByLesson(); - Map> result = Maps.newHashMap(); - - groupByLesson.forEach(classmateInfoGroupByLesson -> result.put(classmateInfoGroupByLesson.getLessonName(), classmateInfoGroupByLesson - .getStudents())); - - return result; - } - - /** - * 查询所有师生关系,包括班主任/学生,任课老师/学生 - * - * @return 师生关系 - */ - public Map> findTeacherStudent() { - List teacherStudentByClass = studentRepo.findTeacherStudentByClass(); - List teacherStudentByLesson = studentRepo.findTeacherStudentByLesson(); - Map> result = Maps.newHashMap(); - - teacherStudentByClass.forEach(teacherStudent -> result.put(teacherStudent.getTeacherName(), Sets.newHashSet(teacherStudent - .getStudents()))); - - teacherStudentByLesson.forEach(teacherStudent -> result.put(teacherStudent.getTeacherName(), Sets.newHashSet(teacherStudent - .getStudents()))); - - return result; - } -} diff --git a/spring-boot-demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java b/spring-boot-demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java deleted file mode 100644 index 9f4fed2..0000000 --- a/spring-boot-demo-neo4j/src/test/java/com/xkcoding/neo4j/Neo4jTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.xkcoding.neo4j; - -import cn.hutool.json.JSONUtil; -import com.xkcoding.neo4j.model.Lesson; -import com.xkcoding.neo4j.model.Student; -import com.xkcoding.neo4j.service.NeoService; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - *

    - * 测试Neo4j - *

    - * - * @package: com.xkcoding.neo4j - * @description: 测试Neo4j - * @author: yangkai.shen - * @date: Created in 2018-12-24 15:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { - @Autowired - private NeoService neoService; - - /** - * 测试保存 - */ - @Test - public void testSave() { - neoService.initData(); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - neoService.delete(); - } - - /** - * 测试查询 鸣人 学了哪些课程 - */ - @Test - public void testFindLessonsByStudent() { - // 深度为1,则课程的任教老师的属性为null - // 深度为2,则会把课程的任教老师的属性赋值 - List lessons = neoService.findLessonsFromStudent("漩涡鸣人", 2); - - lessons.forEach(lesson -> log.info("【lesson】= {}", JSONUtil.toJsonStr(lesson))); - } - - /** - * 测试查询班级人数 - */ - @Test - public void testCountStudent() { - Long all = neoService.studentCount(null); - log.info("【全校人数】= {}", all); - Long seven = neoService.studentCount("第七班"); - log.info("【第七班人数】= {}", seven); - } - - /** - * 测试根据课程查询同学关系 - */ - @Test - public void testFindClassmates() { - Map> classmates = neoService.findClassmatesGroupByLesson(); - classmates.forEach((k, v) -> log.info("因为一起上了【{}】这门课,成为同学关系的有:{}", k, JSONUtil.toJsonStr(v.stream() - .map(Student::getName) - .collect(Collectors.toList())))); - } - - /** - * 查询所有师生关系,包括班主任/学生,任课老师/学生 - */ - @Test - public void testFindTeacherStudent() { - Map> teacherStudent = neoService.findTeacherStudent(); - teacherStudent.forEach((k, v) -> log.info("【{}】教的学生有 {}", k, JSONUtil.toJsonStr(v.stream() - .map(Student::getName) - .collect(Collectors.toList())))); - } -} diff --git a/spring-boot-demo-oauth/pom.xml b/spring-boot-demo-oauth/pom.xml deleted file mode 100644 index dd76db3..0000000 --- a/spring-boot-demo-oauth/pom.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-oauth - 1.0.0-SNAPSHOT - - spring-boot-demo-oauth-authorization-server - spring-boot-demo-oauth-resource-server - - pom - - spring-boot-demo-oauth - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - mysql - mysql-connector-java - runtime - - - - com.h2database - h2 - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - junit - junit - - - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - org.junit.jupiter - junit-jupiter - 5.5.2 - test - - - - - - spring-boot-demo-oauth - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/pom.xml b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/pom.xml deleted file mode 100644 index d4fff86..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - spring-boot-demo-oauth - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-oauth-authorization-server - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - ${spring.boot.version} - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java deleted file mode 100644 index ed73b61..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/SpringBootDemoOauthApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.oauth; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.oauth - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-02-17 23:52 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - * @modified: EchoCow - * @date: Modified in 2020-01-6 21:12 - */ -@SpringBootApplication -public class SpringBootDemoOauthApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOauthApplication.class, args); - } - -} - diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java deleted file mode 100644 index 816ab07..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.oauth.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URLEncoder; - -/** - * 登录失败处理器,失败后携带失败信息重定向到登录地址重新登录. - * - * @author EchoCow - * @date 2020/1/7 下午1:01 - */ -@Slf4j -@Component -public class ClientLoginFailureHandler implements AuthenticationFailureHandler { - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException { - log.debug("Login failed!"); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.sendRedirect("/oauth/login?error=" - + URLEncoder.encode(exception.getLocalizedMessage(), "UTF-8")); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java deleted file mode 100644 index 1737a63..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.oauth.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * 客户团退出登录成功处理器. - * - * @author EchoCow - * @date 2020/1/6 下午22:11 - */ -@Slf4j -@Component -public class ClientLogoutSuccessHandler implements LogoutSuccessHandler { - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - response.setStatus(HttpStatus.FOUND.value()); - // 跳转到客户端的回调地址 - response.sendRedirect(request.getParameter("redirectUrl")); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java deleted file mode 100644 index 787c9f3..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.xkcoding.oauth.config; - -import com.xkcoding.oauth.service.SysClientDetailsService; -import com.xkcoding.oauth.service.SysUserService; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午1:32 - */ -@Configuration -@RequiredArgsConstructor -@EnableAuthorizationServer -public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { - private final SysClientDetailsService sysClientDetailsService; - private final SysUserService sysUserService; - private final TokenStore tokenStore; - private final AuthenticationManager authenticationManager; - private final JwtAccessTokenConverter jwtAccessTokenConverter; - - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) { - endpoints.authenticationManager(authenticationManager) - .userDetailsService(sysUserService) - .tokenStore(tokenStore) - .accessTokenConverter(jwtAccessTokenConverter); - } - - @Override - public void configure(ClientDetailsServiceConfigurer clients) throws Exception { - // 从数据库读取我们自定义的客户端信息 - clients.withClientDetails(sysClientDetailsService); - } - - @Override - public void configure(AuthorizationServerSecurityConfigurer security) { - security - // 获取 token key 需要进行 basic 认证客户端信息 - .tokenKeyAccess("isAuthenticated()") - // 获取 token 信息同样需要 basic 认证客户端信息 - .checkTokenAccess("isAuthenticated()"); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java deleted file mode 100644 index 39ac779..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.xkcoding.oauth.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.io.ClassPathResource; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; - -import java.security.KeyPair; - -/** - * token 相关配置. - * - * @author EchoCow - * @date 2020/1/6 下午1:33 - */ -@Configuration -@RequiredArgsConstructor -public class Oauth2AuthorizationTokenConfig { - - /** - * 声明 内存 TokenStore 实现,用来存储 token 相关. - * 默认实现有 mysql、redis - * - * @return InMemoryTokenStore - */ - @Bean - @Primary - public TokenStore tokenStore() { - return new InMemoryTokenStore(); - } - - /** - * jwt 令牌 配置,非对称加密 - * - * @return 转换器 - */ - @Bean - public JwtAccessTokenConverter jwtAccessTokenConverter() { - final JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); - accessTokenConverter.setKeyPair(keyPair()); - return accessTokenConverter; - } - - /** - * 密钥 keyPair. - * 可用于生成 jwt / jwk. - * - * @return keyPair - */ - @Bean - public KeyPair keyPair() { - KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "123456".toCharArray()); - return keyStoreKeyFactory.getKeyPair("oauth2"); - } - - /** - * 加密方式,使用 BCrypt. - * 参数越大加密次数越多,时间越久. - * 默认为 10. - * - * @return PasswordEncoder - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java deleted file mode 100644 index d6071cb..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.xkcoding.oauth.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -/** - * 安全配置. - * - * @author EchoCow - * @date 2020/1/6 下午1:27 - */ -@EnableWebSecurity -@RequiredArgsConstructor -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - private final ClientLogoutSuccessHandler clientLogoutSuccessHandler; - private final ClientLoginFailureHandler clientLoginFailureHandler; - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .loginPage("/oauth/login") - .failureHandler(clientLoginFailureHandler) - .loginProcessingUrl("/authorization/form") - .and() - .logout() - .logoutUrl("/oauth/logout") - .logoutSuccessHandler(clientLogoutSuccessHandler) - .and() - .authorizeRequests() - .antMatchers("/oauth/**").permitAll() - .anyRequest() - .authenticated(); - } - - /** - * 授权管理. - * - * @return 认证管理对象 - * @throws Exception 认证异常信息 - */ - @Override - @Bean - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java deleted file mode 100644 index 11cfadb..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * spring security oauth2 的相关配置。 - * 使用 spring boot oauth2 自动配置。 - * {@link com.xkcoding.oauth.config.Oauth2AuthorizationServerConfig} - * 授权服务器相关的配置,主要设置授权服务器如何读取客户端、用户信息和一些端点配置 - * 可以在这里配置更多的东西,例如端点映射,token 增强等 - * - * {@link com.xkcoding.oauth.config.Oauth2AuthorizationTokenConfig} - * 授权服务器 token 相关的配置,主要设置 jwt、加密方式等信息 - * - * {@link com.xkcoding.oauth.config.ClientLogoutSuccessHandler} - * 资源服务器退出以后的处理。在授权码模式中,所有的客户端都需要跳转到授权服务器进行登录 - * 当登录成功以后跳转到回调地址,如果用户需要登出,也要跳转到授权服务器这里进行登出 - * 但是 spring security oauth2 似乎并没有这个逻辑。 - * 所以自己给登出端点加了一个 redirect_url 参数,表示登出成功以后要跳转的地址 - * 这个处理器就是来完成登出成功以后的跳转操作的。 - * - * - * @author EchoCow - * @date 2020/1/7 上午9:16 - */ -package com.xkcoding.oauth.config; diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java deleted file mode 100644 index 8175467..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.oauth.controller; - -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.SessionAttributes; -import org.springframework.web.servlet.ModelAndView; - -import java.util.Map; - -/** - * 自定义确认授权页面. - * 需要注意的是: 不能在代码中 setComplete,因为整个授权流程并没有结束 - * 我们只是在中途修改了它确认的一些信息而已。 - * - * @author EchoCow - * @date 2020/1/6 下午4:42 - */ -@Controller -@SessionAttributes("authorizationRequest") -public class AuthorizationController { - - /** - * 自定义确认授权页面 - * 当然你也可以使用 {@link AuthorizationEndpoint#setUserApprovalPage(String)} 方法 - * 进行设置,但是 model 就没有那么灵活了 - * - * @param model model - * @return ModelAndView - */ - @GetMapping("/oauth/confirm_access") - public ModelAndView getAccessConfirmation(Map model) { - AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); - ModelAndView view = new ModelAndView(); - view.setViewName("authorization"); - view.addObject("clientId", authorizationRequest.getClientId()); - // 传递 scope 过去,Set 集合 - view.addObject("scopes", authorizationRequest.getScope()); - return view; - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java deleted file mode 100644 index 5d7aa5d..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/Oauth2Controller.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.xkcoding.oauth.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.client.ResourceAccessException; -import org.springframework.web.servlet.ModelAndView; - -import java.security.Principal; -import java.util.Objects; - -/** - * 页面控制器. - * - * @author EchoCow - * @date 2020/1/6 下午4:30 - */ -@Controller -@RequestMapping("/oauth") -@RequiredArgsConstructor -public class Oauth2Controller { - - /** - * 授权码模式跳转到登录页面 - * - * @return view - */ - @GetMapping("/login") - public String loginView() { - return "login"; - } - - /** - * 退出登录 - * - * @param redirectUrl 退出完成后的回调地址 - * @param principal 用户信息 - * @return 结果 - */ - @GetMapping("/logout") - public ModelAndView logoutView( - @RequestParam("redirect_url") String redirectUrl, Principal principal) { - if (Objects.isNull(principal)) { - throw new ResourceAccessException("请求错误,用户尚未登录"); - } - ModelAndView view = new ModelAndView(); - view.setViewName("logout"); - view.addObject("user", principal.getName()); - view.addObject("redirectUrl", redirectUrl); - return view; - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java deleted file mode 100644 index 453b76c..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * 控制器。除了业务逻辑的以外,提供两个控制器来帮助完成自定义: - * {@link com.xkcoding.oauth.controller.AuthorizationController} - * 自定义的授权控制器,重新设置到我们的界面中去,不使用他的默认实现 - * - * {@link com.xkcoding.oauth.controller.Oauth2Controller} - * 页面跳转的控制器,这里拿出来是因为真的可以做很多事。比如登录的时候携带点什么 - * 或者退出的时候携带什么标识,都可以。 - * - * @author EchoCow - * @date 2020/1/7 上午11:25 - * @see org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint - */ -package com.xkcoding.oauth.controller; diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java deleted file mode 100644 index 535e366..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.xkcoding.oauth.entity; - -import lombok.Data; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; - -import javax.persistence.*; -import java.util.*; -import java.util.stream.Collectors; - -/** - * 客户端信息. - * 这里实现了 ClientDetails 接口 - * 个人建议不应该在实体类里面写任何逻辑代码 - * 而为了避免实体类耦合严重不应该去实现这个接口的 - * 但是这里为了演示和 {@link SysUser} 不同的方式,所以就选择实现这个接口了 - * 另一种方式是写一个方法将它转化为默认实现 {@link BaseClientDetails} 比较好一点并且简单很多 - * - * @author EchoCow - * @date 2020/1/6 下午12:54 - */ -@Data -@Table -@Entity -public class SysClientDetails implements ClientDetails { - - /** - * 主键 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * client id - */ - private String clientId; - - /** - * client 密钥 - */ - private String clientSecret; - - /** - * 资源服务器名称 - */ - private String resourceIds; - - /** - * 授权域 - */ - private String scopes; - - /** - * 授权类型 - */ - private String grantTypes; - - /** - * 重定向地址,授权码时必填 - */ - private String redirectUrl; - - /** - * 授权信息 - */ - private String authorizations; - - /** - * 授权令牌有效时间 - */ - private Integer accessTokenValiditySeconds; - - /** - * 刷新令牌有效时间 - */ - private Integer refreshTokenValiditySeconds; - - /** - * 自动授权请求域 - */ - private String autoApproveScopes; - - /** - * 是否安全 - * - * @return 结果 - */ - @Override - public boolean isSecretRequired() { - return this.clientSecret != null; - } - - /** - * 是否有 scopes - * - * @return 结果 - */ - @Override - public boolean isScoped() { - return this.scopes != null && !this.scopes.isEmpty(); - } - - /** - * scopes - * - * @return scopes - */ - @Override - public Set getScope() { - return stringToSet(scopes); - } - - /** - * 授权类型 - * - * @return 结果 - */ - @Override - public Set getAuthorizedGrantTypes() { - return stringToSet(grantTypes); - } - - @Override - public Set getResourceIds() { - return stringToSet(resourceIds); - } - - - /** - * 获取回调地址 - * - * @return redirectUrl - */ - @Override - public Set getRegisteredRedirectUri() { - return stringToSet(redirectUrl); - } - - /** - * 这里需要提一下 - * 个人觉得这里应该是客户端所有的权限 - * 但是已经有 scope 的存在可以很好的对客户端的权限进行认证了 - * 那么在 oauth2 的四个角色中,这里就有可能是资源服务器的权限 - * 但是一般资源服务器都有自己的权限管理机制,比如拿到用户信息后做 RBAC - * 所以在 spring security 的默认实现中直接给的是空的一个集合 - * 这里我们也给他一个空的把 - * - * @return GrantedAuthority - */ - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - /** - * 判断是否自动授权 - * - * @param scope scope - * @return 结果 - */ - @Override - public boolean isAutoApprove(String scope) { - if (autoApproveScopes == null || autoApproveScopes.isEmpty()) { - return false; - } - Set authorizationSet = stringToSet(authorizations); - for (String auto : authorizationSet) { - if ("true".equalsIgnoreCase(auto) || scope.matches(auto)) { - return true; - } - } - return false; - } - - /** - * additional information 是 spring security 的保留字段 - * 暂时用不到,直接给个空的即可 - * - * @return map - */ - @Override - public Map getAdditionalInformation() { - return Collections.emptyMap(); - } - - private Set stringToSet(String s) { - return Arrays.stream(s.split(",")).collect(Collectors.toSet()); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java deleted file mode 100644 index e6e4f69..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysRole.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.xkcoding.oauth.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.codehaus.jackson.annotate.JsonIgnore; - -import javax.persistence.*; -import java.util.Set; - -/** - * 这里完全可以只用一个字段代替的 - * 但是想了想还是模拟实际的情况来把 - * 角色信息. - * - * @author EchoCow - * @date 2020/1/6 下午12:44 - */ -@Data -@Table -@Entity -@EqualsAndHashCode(exclude = {"users"}) -@ToString(exclude = "users") -public class SysRole { - - /** - * 主键. - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 角色名称,按照 spring security 规范 - * 需要以 ROLE_ 开头. - */ - private String name; - - /** - * 角色描述. - */ - private String description; - - /** - * 当前角色所有用户. - */ - @ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER) - private Set users; -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java deleted file mode 100644 index 84a9641..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysUser.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.xkcoding.oauth.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; - -import javax.persistence.*; -import java.util.Set; - -/** - * 用户实体. - * 避免实体类耦合,所以不去实现 {@link UserDetails} 接口 - * 因为有且只有登录加载用户的时候才会需要这个接口 - * 我们就手动构建一个 {@link User} 的默认实现就可以了 - * 实现接口的方式可以参考 {@link SysClientDetails} - * - * @author EchoCow - * @date 2020/1/6 下午12:41 - */ -@Data -@Table -@Entity -@EqualsAndHashCode(exclude = "roles") -@ToString(exclude = "roles") -public class SysUser { - - /** - * 主键. - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 用户名. - */ - private String username; - - /** - * 密码. - */ - private String password; - - /** - * 当前用户所有角色. - */ - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "sys_user_role", - joinColumns = @JoinColumn(name = "user_id"), - inverseJoinColumns = @JoinColumn(name = "role_id") - ) - private Set roles; -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java deleted file mode 100644 index 1184aca..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysClientDetailsRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xkcoding.oauth.repostiory; - -import com.xkcoding.oauth.entity.SysClientDetails; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; - -import java.util.Optional; - -/** - * 客户端信息. - * - * @author EchoCow - * @date 2020/1/6 下午1:09 - */ -public interface SysClientDetailsRepository extends JpaRepository { - - /** - * 通过 clientId 查找客户端信息. - * - * @param clientId clientId - * @return 结果 - */ - Optional findFirstByClientId(String clientId); - - /** - * 根据客户端 id 删除客户端 - * - * @param clientId 客户端id - */ - @Modifying - void deleteByClientId(String clientId); - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java deleted file mode 100644 index a5aaff9..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/repostiory/SysUserRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.oauth.repostiory; - -import com.xkcoding.oauth.entity.SysUser; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -/** - * 用户信息仓库. - * - * @author EchoCow - * @date 2020/1/6 下午1:08 - */ -public interface SysUserRepository extends JpaRepository { - - /** - * 通过用户名查找用户. - * - * @param username 用户名 - * @return 结果 - */ - Optional findFirstByUsername(String username); - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java deleted file mode 100644 index 408414a..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysClientDetailsService.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.xkcoding.oauth.service; - -import com.xkcoding.oauth.entity.SysClientDetails; -import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.ClientRegistrationService; -import org.springframework.security.oauth2.provider.NoSuchClientException; - -import java.util.List; - -/** - * 声明自己的实现. - * 参见 {@link ClientRegistrationService} - * - * @author EchoCow - * @date 2020/1/6 下午1:39 - */ -public interface SysClientDetailsService extends ClientDetailsService { - - /** - * 通过客户端 id 查询 - * - * @param clientId 客户端 id - * @return 结果 - */ - SysClientDetails findByClientId(String clientId); - - /** - * 添加客户端信息. - * - * @param clientDetails 客户端信息 - * @throws ClientAlreadyExistsException 客户端已存在 - */ - void addClientDetails(SysClientDetails clientDetails) throws ClientAlreadyExistsException; - - /** - * 更新客户端信息,不包括 clientSecret. - * - * @param clientDetails 客户端信息 - * @throws NoSuchClientException 找不到客户端异常 - */ - void updateClientDetails(SysClientDetails clientDetails) throws NoSuchClientException; - - /** - * 更新客户端密钥. - * - * @param clientId 客户端 id - * @param clientSecret 客户端密钥 - * @throws NoSuchClientException 找不到客户端异常 - */ - void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException; - - /** - * 删除客户端信息. - * - * @param clientId 客户端 id - * @throws NoSuchClientException 找不到客户端异常 - */ - void removeClientDetails(String clientId) throws NoSuchClientException; - - /** - * 查询所有 - * - * @return 结果 - */ - List findAll(); -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java deleted file mode 100644 index 6604a54..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/SysUserService.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.xkcoding.oauth.service; - -import com.xkcoding.oauth.entity.SysUser; -import org.springframework.security.core.userdetails.UserDetailsService; - -import java.util.List; - - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午3:44 - */ -public interface SysUserService extends UserDetailsService { - /** - * 查询所有用户 - * - * @return 用户 - */ - List findAll(); - - /** - * 通过 id 查询用户 - * - * @param id id - * @return 用户 - */ - SysUser findById(Long id); - - /** - * 创建用户 - * - * @param sysUser 用户 - */ - void createUser(SysUser sysUser); - - /** - * 更新用户 - * - * @param sysUser 用户 - */ - void updateUser(SysUser sysUser); - - /** - * 更新用户 密码 - * - * @param id 用户 id - * @param password 用户密码 - */ - void updatePassword(Long id, String password); - - /** - * 删除用户. - * - * @param id id - */ - void deleteUser(Long id); -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java deleted file mode 100644 index 00e3662..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysClientDetailsServiceImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.xkcoding.oauth.service.impl; - -import com.xkcoding.oauth.entity.SysClientDetails; -import com.xkcoding.oauth.repostiory.SysClientDetailsRepository; -import com.xkcoding.oauth.service.SysClientDetailsService; -import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.provider.*; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * 客户端 相关操作. - * - * @author EchoCow - * @date 2020/1/6 下午1:37 - */ -@Service -@RequiredArgsConstructor -public class SysClientDetailsServiceImpl implements SysClientDetailsService { - - private final SysClientDetailsRepository sysClientDetailsRepository; - private final PasswordEncoder passwordEncoder; - - @Override - public ClientDetails loadClientByClientId(String id) throws ClientRegistrationException { - return sysClientDetailsRepository.findFirstByClientId(id) - .orElseThrow(() -> new ClientRegistrationException("Loading client exception.")); - } - - @Override - public SysClientDetails findByClientId(String clientId) { - return sysClientDetailsRepository.findFirstByClientId(clientId) - .orElseThrow(() -> new ClientRegistrationException("Loading client exception.")); - } - - @Override - public void addClientDetails(SysClientDetails clientDetails) throws ClientAlreadyExistsException { - clientDetails.setId(null); - if (sysClientDetailsRepository.findFirstByClientId(clientDetails.getClientId()).isPresent()) { - throw new ClientAlreadyExistsException(String.format("Client id %s already exist.", clientDetails.getClientId())); - } - sysClientDetailsRepository.save(clientDetails); - } - - @Override - public void updateClientDetails(SysClientDetails clientDetails) throws NoSuchClientException { - SysClientDetails exist = sysClientDetailsRepository.findFirstByClientId(clientDetails.getClientId()) - .orElseThrow(() -> new NoSuchClientException("No such client!")); - clientDetails.setClientSecret(exist.getClientSecret()); - sysClientDetailsRepository.save(clientDetails); - } - - @Override - public void updateClientSecret(String clientId, String clientSecret) throws NoSuchClientException { - SysClientDetails exist = sysClientDetailsRepository.findFirstByClientId(clientId) - .orElseThrow(() -> new NoSuchClientException("No such client!")); - exist.setClientSecret(passwordEncoder.encode(clientSecret)); - sysClientDetailsRepository.save(exist); - } - - @Override - public void removeClientDetails(String clientId) throws NoSuchClientException { - sysClientDetailsRepository.deleteByClientId(clientId); - } - - @Override - public List findAll() { - return sysClientDetailsRepository.findAll(); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java deleted file mode 100644 index 307af4d..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/impl/SysUserServiceImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.xkcoding.oauth.service.impl; - -import com.xkcoding.oauth.entity.SysUser; -import com.xkcoding.oauth.repostiory.SysUserRepository; -import com.xkcoding.oauth.service.SysUserService; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 用户相关操作. - * - * @author EchoCow - * @date 2020/1/6 下午3:06 - */ -@Service -@RequiredArgsConstructor -public class SysUserServiceImpl implements SysUserService { - - private final SysUserRepository sysUserRepository; - private final PasswordEncoder passwordEncoder; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - SysUser sysUser = sysUserRepository.findFirstByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found!")); - List roles = sysUser.getRoles().stream() - .map(sysRole -> new SimpleGrantedAuthority(sysRole.getName())) - .collect(Collectors.toList()); - // 在这里手动构建 UserDetails 的默认实现 - return new User(sysUser.getUsername(), sysUser.getPassword(), roles); - } - - @Override - public List findAll() { - return sysUserRepository.findAll(); - } - - @Override - public SysUser findById(Long id) { - return sysUserRepository.findById(id) - .orElseThrow(() -> new RuntimeException("找不到用户")); - } - - @Override - public void createUser(SysUser sysUser) { - sysUser.setId(null); - sysUserRepository.save(sysUser); - } - - @Override - public void updateUser(SysUser sysUser) { - sysUser.setPassword(null); - sysUserRepository.save(sysUser); - } - - @Override - public void updatePassword(Long id, String password) { - SysUser exist = findById(id); - exist.setPassword(passwordEncoder.encode(password)); - sysUserRepository.save(exist); - } - - @Override - public void deleteUser(Long id) { - sysUserRepository.deleteById(id); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java deleted file mode 100644 index 45f57f5..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/main/java/com/xkcoding/oauth/service/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * service 层,继承并实现 spring 接口. - * - * @author EchoCow - * @date 2020/1/7 上午9:16 - */ -package com.xkcoding.oauth.service; diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java deleted file mode 100644 index 3dc8233..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/PasswordEncodeTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.oauth; - -import org.junit.jupiter.api.Test; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午3:51 - */ -public class PasswordEncodeTest { - - private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - - @Test - public void getPasswordWhenPassed() { - System.out.println(passwordEncoder.encode("oauth2")); - System.out.println(passwordEncoder.encode("123456")); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java deleted file mode 100644 index 01e0d44..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationCodeGrantTests.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.xkcoding.oauth.oauth; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; -import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.xkcoding.oauth.oauth.AuthorizationServerInfo.getUrl; -import static org.junit.jupiter.api.Assertions.*; - -/** - * 授权码模式测试. - * - * @author EchoCow - * @date 2020/1/6 下午8:43 - */ -public class AuthorizationCodeGrantTests { - - private AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); - private AuthorizationServerInfo authorizationServerInfo = new AuthorizationServerInfo(); - - @BeforeEach - void setUp() { - resource.setAccessTokenUri(getUrl("/oauth/token")); - resource.setClientId("oauth2"); - resource.setId("oauth2"); - resource.setScope(Arrays.asList("READ", "WRITE")); - resource.setAccessTokenUri(getUrl("/oauth/token")); - resource.setUserAuthorizationUri(getUrl("/oauth/authorize")); - } - - @Test - void testCannotConnectWithoutToken() { - OAuth2RestTemplate template = new OAuth2RestTemplate(resource); - assertThrows(UserRedirectRequiredException.class, - () -> template.getForObject(getUrl("/oauth/me"), String.class)); - } - - @Test - void testAttemptedTokenAcquisitionWithNoRedirect() { - AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider(); - assertThrows(UserRedirectRequiredException.class, - () -> provider.obtainAccessToken(resource, new DefaultAccessTokenRequest())); - } - - /** - * 这里不使用他提供的是因为很多地方不符合我们的需要 - * 比如 csrf,比如许多有些是自己自定义的端点这些 - * 所以只有我们一步一步的来进行测试拿到授权码 - */ - @Test - void testCodeAcquisitionWithCorrectContext() { - // 1. 请求登录页面获取 _csrf 的 value 以及 cookie - ResponseEntity page = authorizationServerInfo.getForString("/oauth/login"); - assertNotNull(page.getBody()); - String cookie = page.getHeaders().getFirst("Set-Cookie"); - HttpHeaders headers = new HttpHeaders(); - headers.set("Cookie", cookie); - Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); - assertTrue(matcher.find()); - - // 2. 添加表单数据 - MultiValueMap form = new LinkedMultiValueMap<>(); - form.add("username", "admin"); - form.add("password", "123456"); - form.add("_csrf", matcher.group(1)); - - // 3. 登录授权并获取登录成功的 cookie - ResponseEntity response = authorizationServerInfo - .postForStatus("/authorization/form", headers, form); - assertNotNull(response); - cookie = response.getHeaders().getFirst("Set-Cookie"); - headers = new HttpHeaders(); - headers.set("Cookie", cookie); - headers.setAccept(Collections.singletonList(MediaType.ALL)); - - // 4. 请求到 确认授权页面 ,获取确认授权页面的 _csrf 的 value - ResponseEntity confirm = authorizationServerInfo - .getForString("/oauth/authorize?response_type=code&client_id=oauth2&redirect_uri=http://example.com&scope=READ", headers); - - headers = confirm.getHeaders(); - // 确认过一次后,后面都会自动确认了,这里判断下是不是重定向请求 - // 如果不是,就表示是第一次,需要确认授权 - if (!confirm.getStatusCode().is3xxRedirection()) { - assertNotNull(confirm.getBody()); - Matcher matcherConfirm = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(confirm.getBody()); - assertTrue(matcherConfirm.find()); - headers = new HttpHeaders(); - headers.set("Cookie", cookie); - headers.setAccept(Collections.singletonList(MediaType.ALL)); - - // 5. 构建 同意授权 的表单 - form = new LinkedMultiValueMap<>(); - form.add("user_oauth_approval", "true"); - form.add("scope.READ", "true"); - form.add("_csrf", matcherConfirm.group(1)); - - // 6. 请求授权,获取 授权码 - headers = authorizationServerInfo.postForHeaders("/oauth/authorize", form, headers); - } - - URI location = headers.getLocation(); - assertNotNull(location); - String query = location.getQuery(); - assertNotNull(query); - String[] result = query.split("="); - assertEquals(2, result.length); - System.out.println(result[1]); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java deleted file mode 100644 index 0c22919..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/AuthorizationServerInfo.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.xkcoding.oauth.oauth; - -import org.springframework.http.*; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RequestCallback; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.net.HttpURLConnection; - -/** - * 授权服务器工具类. - * - * @author EchoCow - * @date 2020/1/6 下午8:44 - */ -@SuppressWarnings("all") -public class AuthorizationServerInfo { - public static final String HOST = "http://127.0.0.1:8080"; - - private RestTemplate client; - - public AuthorizationServerInfo() { - client = new RestTemplate(); - client.setRequestFactory(new SimpleClientHttpRequestFactory() { - @Override - protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { - super.prepareConnection(connection, httpMethod); - connection.setInstanceFollowRedirects(false); - } - }); - client.setErrorHandler(new ResponseErrorHandler() { - public boolean hasError(ClientHttpResponse response) { - return false; - } - - public void handleError(ClientHttpResponse response) { - } - }); - } - - public ResponseEntity getForString(String path, final HttpHeaders headers) { - return client.exchange(getUrl(path), HttpMethod.GET, new HttpEntity<>(null, headers), String.class); - } - - public ResponseEntity getForString(String path) { - return getForString(path, new HttpHeaders()); - } - - public ResponseEntity postForStatus(String path, HttpHeaders headers, MultiValueMap formData) { - HttpHeaders actualHeaders = new HttpHeaders(); - actualHeaders.putAll(headers); - actualHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - return client.exchange(getUrl(path), HttpMethod.POST, - new HttpEntity<>(formData, actualHeaders), (Class) null); - } - - - public static String getUrl(String path) { - return HOST + path; - } - - public HttpHeaders postForHeaders(String path, MultiValueMap formData, final HttpHeaders headers) { - RequestCallback requestCallback = new NullRequestCallback(); - if (headers != null) { - requestCallback = request -> request.getHeaders().putAll(headers); - } - StringBuilder builder = new StringBuilder(getUrl(path)); - if (!path.contains("?")) { - builder.append("?"); - } else { - builder.append("&"); - } - for (String key : formData.keySet()) { - for (String value : formData.get(key)) { - builder.append(key).append("=").append(value); - builder.append("&"); - } - } - builder.deleteCharAt(builder.length() - 1); - - return client.execute(builder.toString(), HttpMethod.POST, requestCallback, - HttpMessage::getHeaders); - } - - private static final class NullRequestCallback implements RequestCallback { - public void doWithRequest(ClientHttpRequest request) { - } - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java deleted file mode 100644 index 38d8d1d..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/oauth/ResourceOwnerPasswordGrantTests.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.oauth.oauth; - -import org.junit.jupiter.api.Test; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; -import org.springframework.security.oauth2.common.OAuth2AccessToken; - -import java.util.Arrays; - -import static com.xkcoding.oauth.oauth.AuthorizationServerInfo.getUrl; -import static org.junit.jupiter.api.Assertions.*; - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午9:14 - */ -public class ResourceOwnerPasswordGrantTests { - - @Test - void testConnectDirectlyToResourceServer() { - assertNotNull(accessToken()); - } - - public static String accessToken() { - ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); - resource.setAccessTokenUri(getUrl("/oauth/token")); - resource.setClientId("oauth2"); - resource.setClientSecret("oauth2"); - resource.setId("oauth2"); - resource.setScope(Arrays.asList("READ", "WRITE")); - resource.setUsername("admin"); - resource.setPassword("123456"); - OAuth2RestTemplate template = new OAuth2RestTemplate(resource); - OAuth2AccessToken accessToken = template.getAccessToken(); - return accessToken.getValue(); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java deleted file mode 100644 index c0126bc..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysClientDetailsTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.oauth.repostiory; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午1:10 - */ -@DataJpaTest -public class SysClientDetailsTest { - @Autowired - private SysClientDetailsRepository sysClientDetailsRepository; - - @Test - public void autowiredSuccessWhenPassed() { - assertNotNull(sysClientDetailsRepository); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java deleted file mode 100644 index 7df0679..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-authorization-server/src/test/java/com/xkcoding/oauth/repostiory/SysUserRepositoryTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.oauth.repostiory; - -import com.xkcoding.oauth.entity.SysUser; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - - -/** - * . - * - * @author EchoCow - * @date 2020/1/6 下午1:25 - */ -@DataJpaTest -public class SysUserRepositoryTest { - - @Autowired - private SysUserRepository sysUserRepository; - - @Test - public void autowiredSuccessWhenPassed() { - assertNotNull(sysUserRepository); - } - - @Test - @DisplayName("测试关联查询") - public void queryUserAndRoleWhenPassed() { - Optional admin = sysUserRepository.findFirstByUsername("admin"); - assertTrue(admin.isPresent()); - SysUser sysUser = admin.orElseGet(SysUser::new); - assertNotNull(sysUser.getRoles()); - assertEquals(1, sysUser.getRoles().size()); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/pom.xml b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/pom.xml deleted file mode 100644 index b19d74c..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - spring-boot-demo-oauth - com.xkcoding - 1.0.0-SNAPSHOT - - 4.0.0 - - spring-boot-demo-oauth-resource-server - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - ${spring.boot.version} - - - diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java deleted file mode 100644 index 33b7bd9..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/SpringBootDemoResourceApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.oauth; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; - -/** - * 启动器. - * - * @author EchoCow - * @date 2020/1/9 上午11:38 - * @version V1.0 - */ -@EnableResourceServer -@SpringBootApplication -public class SpringBootDemoResourceApplication { - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoResourceApplication.class, args); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java deleted file mode 100644 index 2d3243e..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceServerConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.oauth.config; - -import lombok.AllArgsConstructor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.token.TokenStore; - -/** - * 资源服务器配置. - * 我们自己实现了它的配置,所以它的自动装配不会生效 - * - * @author EchoCow - * @date 2020/1/9 下午2:20 - */ -@Configuration -@AllArgsConstructor -@EnableResourceServer -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class OauthResourceServerConfig extends ResourceServerConfigurerAdapter { - - private final ResourceServerProperties resourceServerProperties; - private final TokenStore tokenStore; - - @Override - public void configure(ResourceServerSecurityConfigurer resources) { - resources - .tokenStore(tokenStore) - .resourceId(resourceServerProperties.getResourceId()); - } - - @Override - public void configure(HttpSecurity http) throws Exception { - super.configure(http); - // 前后端分离下,可以关闭 csrf - http.csrf().disable(); - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java deleted file mode 100644 index c28c72c..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/config/OauthResourceTokenConfig.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.xkcoding.oauth.config; - -import cn.hutool.json.JSONObject; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.Base64; - -/** - * token 相关配置,jwt 相关. - * - * @author EchoCow - * @date 2020/1/9 下午2:39 - */ -@Slf4j -@Configuration -@AllArgsConstructor -public class OauthResourceTokenConfig { - - private final ResourceServerProperties resourceServerProperties; - - /** - * 这里并不是对令牌的存储,他将访问令牌与身份验证进行转换 - * 在需要 {@link TokenStore} 的任何地方可以使用此方法 - * - * @return TokenStore - */ - @Bean - public TokenStore tokenStore() { - return new JwtTokenStore(jwtAccessTokenConverter()); - } - - /** - * jwt 令牌转换 - * - * @return jwt - */ - @Bean - public JwtAccessTokenConverter jwtAccessTokenConverter() { - JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); - converter.setVerifierKey(getPubKey()); - return converter; - } - - /** - * 非对称密钥加密,获取 public key。 - * 自动选择加载方式。 - * - * @return public key - */ - private String getPubKey() { - // 如果本地没有密钥,就从授权服务器中获取 - return StringUtils.isEmpty(resourceServerProperties.getJwt().getKeyValue()) - ? getKeyFromAuthorizationServer() - : resourceServerProperties.getJwt().getKeyValue(); - } - - /** - * 本地没有公钥的时候,从服务器上获取 - * 需要进行 Basic 认证 - * - * @return public key - */ - private String getKeyFromAuthorizationServer() { - ObjectMapper objectMapper = new ObjectMapper(); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.AUTHORIZATION, encodeClient()); - HttpEntity requestEntity = new HttpEntity<>(null, httpHeaders); - String pubKey = new RestTemplate() - .getForObject(resourceServerProperties.getJwt().getKeyUri(), String.class, requestEntity); - try { - JSONObject body = objectMapper.readValue(pubKey, JSONObject.class); - log.info("Get Key From Authorization Server."); - return body.getStr("value"); - } catch (IOException e) { - log.error("Get public key error: {}", e.getMessage()); - } - return null; - } - - /** - * 客户端信息 - * - * @return basic - */ - private String encodeClient() { - return "Basic " + Base64.getEncoder().encodeToString((resourceServerProperties.getClientId() - + ":" + resourceServerProperties.getClientSecret()).getBytes()); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java deleted file mode 100644 index 9c6ed62..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/main/java/com/xkcoding/oauth/controller/TestController.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.xkcoding.oauth.controller; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 测试接口. - * - * @author EchoCow - * @date 2020/1/9 下午2:37 - */ -@RestController -public class TestController { - - /** - * 拥有 ROLE_ADMIN 的用户才能访问的资源 - * - * @return ADMIN - */ - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/admin") - public String admin() { - return "ADMIN"; - } - - /** - * 拥有 ROLE_TEST 的用户才能访问的资源 - * - * @return TEST - */ - @PreAuthorize("hasRole('TEST')") - @GetMapping("/test") - public String test() { - return "TEST"; - } - - /** - * scope 有 READ 的用户资源才能访问 - * - * @return READ - */ - @PreAuthorize("#oauth2.hasScope('READ')") - @GetMapping("/read") - public String read() { - return "READ"; - } - - /** - * scope 有 WRITE 的用户资源才能访问 - * - * @return WRITE - */ - @PreAuthorize("#oauth2.hasScope('WRITE')") - @GetMapping("/write") - public String write() { - return "WRITE"; - } - -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java deleted file mode 100644 index 774a4ec..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/AuthorizationTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.oauth; - -import org.junit.jupiter.api.Test; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; - -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * . - * - * @author EchoCow - * @date 2020/1/9 下午3:44 - */ -public class AuthorizationTest { - public static final String AUTHORIZATION_SERVER = "http://127.0.0.1:8080"; - - protected OAuth2RestTemplate oauth2RestTemplate(String username, String password, List scope) { - ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); - resource.setAccessTokenUri(AUTHORIZATION_SERVER + "/oauth/token"); - resource.setClientId("oauth2"); - resource.setClientSecret("oauth2"); - resource.setId("oauth2"); - resource.setScope(scope); - resource.setUsername(username); - resource.setPassword(password); - return new OAuth2RestTemplate(resource); - } - - @Test - void testAccessTokenWhenPassed() { - assertNotNull(oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")) - .getAccessToken()); - } -} diff --git a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java b/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java deleted file mode 100644 index ea0a432..0000000 --- a/spring-boot-demo-oauth/spring-boot-demo-oauth-resource-server/src/test/java/com/xkcoding/oauth/controller/TestControllerTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.xkcoding.oauth.controller; - -import com.xkcoding.oauth.AuthorizationTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; - -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.springframework.http.HttpMethod.GET; - -/** - * . - * - * @author EchoCow - * @date 2020/1/9 下午3:46 - */ -public class TestControllerTest extends AuthorizationTest { - - private static final String URL = "http://127.0.0.1:8081"; - - @Test - @DisplayName("ROLE_ADMIN 角色测试") - void testAdminRoleSucceedAndTestRoleFailedWhenPassed() { - OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")); - ResponseEntity response = template.exchange(URL + "/admin", GET, null, String.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("ADMIN", response.getBody()); - assertThrows(OAuth2AccessDeniedException.class, - () -> template.exchange(URL + "/test", GET, null, String.class)); - } - - @Test - @DisplayName("ROLE_Test 角色测试") - void testTestRoleSucceedWhenPassed() { - OAuth2RestTemplate template = oauth2RestTemplate("test", "123456", Collections.singletonList("READ")); - ResponseEntity response = template.exchange(URL + "/test", GET, null, String.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("TEST", response.getBody()); - assertThrows(OAuth2AccessDeniedException.class, - () -> template.exchange(URL + "/admin", GET, null, String.class)); - } - - @Test - @DisplayName("SCOPE_READ 授权域测试") - void testScopeReadWhenPassed() { - OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("READ")); - ResponseEntity response = template.exchange(URL + "/read", GET, null, String.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("READ", response.getBody()); - assertThrows(OAuth2AccessDeniedException.class, - () -> template.exchange(URL + "/write", GET, null, String.class)); - } - - @Test - @DisplayName("SCOPE_WRITE 授权域测试") - void testScopeWriteWhenPassed() { - OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Collections.singletonList("WRITE")); - ResponseEntity response = template.exchange(URL + "/write", GET, null, String.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("WRITE", response.getBody()); - assertThrows(OAuth2AccessDeniedException.class, - () -> template.exchange(URL + "/read", GET, null, String.class)); - } - - @Test - @DisplayName("SCOPE 测试") - void testScopeWhenPassed() { - OAuth2RestTemplate template = oauth2RestTemplate("admin", "123456", Arrays.asList("READ", "WRITE")); - ResponseEntity writeResponse = template.exchange(URL + "/write", GET, null, String.class); - assertEquals(HttpStatus.OK, writeResponse.getStatusCode()); - assertEquals("WRITE", writeResponse.getBody()); - ResponseEntity readResponse = template.exchange(URL + "/read", GET, null, String.class); - assertEquals(HttpStatus.OK, readResponse.getStatusCode()); - assertEquals("READ", readResponse.getBody()); - } -} diff --git a/spring-boot-demo-orm-beetlsql/README.md b/spring-boot-demo-orm-beetlsql/README.md deleted file mode 100644 index 08cdf57..0000000 --- a/spring-boot-demo-orm-beetlsql/README.md +++ /dev/null @@ -1,388 +0,0 @@ -# spring-boot-demo-orm-beetlsql - -> 此 demo 主要演示了 Spring Boot 如何整合 beetl sql 快捷操作数据库,使用的是beetl官方提供的beetl-framework-starter集成。集成过程不是十分顺利,没有其他的orm框架集成的便捷。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-beetlsql - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-beetlsql - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.68.RELEASE - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - com.ibeetl - beetl-framework-starter - ${ibeetl.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-beetlsql - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -> 注意下方注释的地方,**不能解开注释,并且需要通过JavaConfig的方式手动配置数据源**,否则,会导致beetl启动失败,因此,初始化数据库的数据,只能手动在数据库使用 resources/db 下的建表语句和数据库初始化数据。 - -```yaml -spring: - datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver -#### beetlsql starter不能开启下面选项 -# type: com.zaxxer.hikari.HikariDataSource -# initialization-mode: always -# continue-on-error: true -# schema: -# - "classpath:db/schema.sql" -# data: -# - "classpath:db/data.sql" -# hikari: -# minimum-idle: 5 -# connection-test-query: SELECT 1 FROM DUAL -# maximum-pool-size: 20 -# auto-commit: true -# idle-timeout: 30000 -# pool-name: SpringBootDemoHikariCP -# max-lifetime: 60000 -# connection-timeout: 30000 -logging: - level: - com.xkcoding: debug - com.xkcoding.orm.beetlsql: trace -beetl: - enabled: false -beetlsql: - enabled: true - sqlPath: /sql - daoSuffix: Dao - basePackage: com.xkcoding.orm.beetlsql.dao - dbStyle: org.beetl.sql.core.db.MySqlStyle - nameConversion: org.beetl.sql.core.UnderlinedNameConversion -beet-beetlsql: - dev: true -``` - -## BeetlConfig.java - -```java -/** - *

    - * Beetl数据源配置 - *

    - * - * @package: com.xkcoding.orm.beetlsql.config - * @description: Beetl数据源配置 - * @author: yangkai.shen - * @date: Created in 2018/11/14 17:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class BeetlConfig { - - /** - * Beetl需要显示的配置数据源,方可启动项目,大坑,切记! - */ - @Bean(name = "datasource") - public DataSource getDataSource(Environment env){ - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name")); - dataSource.setJdbcUrl(env.getProperty("spring.datasource.url")); - dataSource.setUsername(env.getProperty("spring.datasource.username")); - dataSource.setPassword(env.getProperty("spring.datasource.password")); - return dataSource; - } -} -``` - -## UserDao.java - -```java -/** - *

    - * UserDao - *

    - * - * @package: com.xkcoding.orm.beetlsql.dao - * @description: UserDao - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface UserDao extends BaseMapper { - -} -``` - -## UserServiceImpl.java - -```java -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.beetlsql.service.impl - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - - private final UserDao userDao; - - @Autowired - public UserServiceImpl(UserDao userDao) { - this.userDao = userDao; - } - - /** - * 新增用户 - * - * @param user 用户 - */ - @Override - public User saveUser(User user) { - userDao.insert(user, true); - return user; - } - - /** - * 批量插入用户 - * - * @param users 用户列表 - */ - @Override - public void saveUserList(List users) { - userDao.insertBatch(users); - } - - /** - * 根据主键删除用户 - * - * @param id 主键 - */ - @Override - public void deleteUser(Long id) { - userDao.deleteById(id); - } - - /** - * 更新用户 - * - * @param user 用户 - * @return 更新后的用户 - */ - @Override - public User updateUser(User user) { - if (ObjectUtil.isNull(user)) { - throw new RuntimeException("用户id不能为null"); - } - userDao.updateTemplateById(user); - return userDao.single(user.getId()); - } - - /** - * 查询单个用户 - * - * @param id 主键id - * @return 用户信息 - */ - @Override - public User getUser(Long id) { - return userDao.single(id); - } - - /** - * 查询用户列表 - * - * @return 用户列表 - */ - @Override - public List getUserList() { - return userDao.all(); - } - - /** - * 分页查询 - * - * @param currentPage 当前页 - * @param pageSize 每页条数 - * @return 分页用户列表 - */ - @Override - public PageQuery getUserByPage(Integer currentPage, Integer pageSize) { - return userDao.createLambdaQuery().page(currentPage, pageSize); - } -} -``` - -## UserServiceTest.java - -```java -/** - *

    - * User Service测试 - *

    - * - * @package: com.xkcoding.orm.beetlsql.service - * @description: User Service测试 - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoOrmBeetlsqlApplicationTests { - @Autowired - private UserService userService; - - @Test - public void saveUser() { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - - user = userService.saveUser(user); - Assert.assertTrue(ObjectUtil.isNotNull(user.getId())); - log.debug("【user】= {}", user); - } - - @Test - public void saveUserList() { - List users = Lists.newArrayList(); - for (int i = 5; i < 15; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - users.add(user); - } - userService.saveUserList(users); - Assert.assertTrue(userService.getUserList().size() > 2); - } - - @Test - public void deleteUser() { - userService.deleteUser(1L); - User user = userService.getUser(1L); - Assert.assertTrue(ObjectUtil.isNull(user)); - } - - @Test - public void updateUser() { - User user = userService.getUser(2L); - user.setName("beetlSql 修改后的名字"); - User update = userService.updateUser(user); - Assert.assertEquals("beetlSql 修改后的名字", update.getName()); - log.debug("【update】= {}", update); - } - - @Test - public void getUser() { - User user = userService.getUser(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - @Test - public void getUserList() { - List userList = userService.getUserList(); - Assert.assertTrue(CollUtil.isNotEmpty(userList)); - log.debug("【userList】= {}", userList); - } - - @Test - public void getUserByPage() { - List userList = userService.getUserList(); - PageQuery userByPage = userService.getUserByPage(1, 5); - Assert.assertEquals(5, userByPage.getList().size()); - Assert.assertEquals(userList.size(), userByPage.getTotalRow()); - log.debug("【userByPage】= {}", JSONUtil.toJsonStr(userByPage)); - } -} -``` - -## 参考 - -- BeetlSQL官方文档:http://ibeetl.com/guide/#beetlsql -- 开源项目:https://gitee.com/yangkb/springboot-beetl-beetlsql -- 博客:https://blog.csdn.net/flystarfly/article/details/82752597 \ No newline at end of file diff --git a/spring-boot-demo-orm-beetlsql/pom.xml b/spring-boot-demo-orm-beetlsql/pom.xml deleted file mode 100644 index 0d2c42c..0000000 --- a/spring-boot-demo-orm-beetlsql/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-beetlsql - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-beetlsql - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.68.RELEASE - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - com.ibeetl - beetl-framework-starter - ${ibeetl.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-beetlsql - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java deleted file mode 100644 index 825efe6..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/SpringBootDemoOrmBeetlsqlApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.orm.beetlsql; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.orm.beetlsql - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/11/14 15:47 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoOrmBeetlsqlApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmBeetlsqlApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java deleted file mode 100644 index 2727160..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/config/BeetlConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.orm.beetlsql.config; - -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -import javax.sql.DataSource; - -/** - *

    - * Beetl数据源配置 - *

    - * - * @package: com.xkcoding.orm.beetlsql.config - * @description: Beetl数据源配置 - * @author: yangkai.shen - * @date: Created in 2018/11/14 17:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class BeetlConfig { - - /** - * Beetl需要显示的配置数据源,方可启动项目,大坑,切记! - */ - @Bean(name = "datasource") - public DataSource getDataSource(Environment env){ - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name")); - dataSource.setJdbcUrl(env.getProperty("spring.datasource.url")); - dataSource.setUsername(env.getProperty("spring.datasource.username")); - dataSource.setPassword(env.getProperty("spring.datasource.password")); - return dataSource; - } -} diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java deleted file mode 100644 index 63238d5..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/dao/UserDao.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xkcoding.orm.beetlsql.dao; - -import com.xkcoding.orm.beetlsql.entity.User; -import org.beetl.sql.core.mapper.BaseMapper; -import org.springframework.stereotype.Component; - -/** - *

    - * UserDao - *

    - * - * @package: com.xkcoding.orm.beetlsql.dao - * @description: UserDao - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface UserDao extends BaseMapper { - -} diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java deleted file mode 100644 index 71e2fd3..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/entity/User.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.xkcoding.orm.beetlsql.entity; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.beetl.sql.core.annotatoin.Table; - -import java.io.Serializable; -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.beetlsql.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:06 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Table(name = "orm_user") -public class User implements Serializable { - private static final long serialVersionUID = -1840831686851699943L; - - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 上次登录时间 - */ - private Date lastLoginTime; - - /** - * 上次更新时间 - */ - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java deleted file mode 100644 index a0e7af1..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/UserService.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.xkcoding.orm.beetlsql.service; - -import com.xkcoding.orm.beetlsql.entity.User; -import org.beetl.sql.core.engine.PageQuery; - -import java.util.List; - -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.beetlsql.service - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService { - /** - * 新增用户 - * - * @param user 用户 - * @return 保存的用户 - */ - User saveUser(User user); - - - /** - * 批量插入用户 - * - * @param users 用户列表 - */ - void saveUserList(List users); - - /** - * 根据主键删除用户 - * - * @param id 主键 - */ - void deleteUser(Long id); - - /** - * 更新用户 - * - * @param user 用户 - * @return 更新后的用户 - */ - User updateUser(User user); - - /** - * 查询单个用户 - * - * @param id 主键id - * @return 用户信息 - */ - User getUser(Long id); - - /** - * 查询用户列表 - * - * @return 用户列表 - */ - List getUserList(); - - /** - * 分页查询 - * - * @param currentPage 当前页 - * @param pageSize 每页条数 - * @return 分页用户列表 - */ - PageQuery getUserByPage(Integer currentPage, Integer pageSize); -} diff --git a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java b/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java deleted file mode 100644 index f4bad10..0000000 --- a/spring-boot-demo-orm-beetlsql/src/main/java/com/xkcoding/orm/beetlsql/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.xkcoding.orm.beetlsql.service.impl; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.orm.beetlsql.dao.UserDao; -import com.xkcoding.orm.beetlsql.entity.User; -import com.xkcoding.orm.beetlsql.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.beetl.sql.core.engine.PageQuery; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.beetlsql.service.impl - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class UserServiceImpl implements UserService { - - private final UserDao userDao; - - @Autowired - public UserServiceImpl(UserDao userDao) { - this.userDao = userDao; - } - - /** - * 新增用户 - * - * @param user 用户 - */ - @Override - public User saveUser(User user) { - userDao.insert(user, true); - return user; - } - - /** - * 批量插入用户 - * - * @param users 用户列表 - */ - @Override - public void saveUserList(List users) { - userDao.insertBatch(users); - } - - /** - * 根据主键删除用户 - * - * @param id 主键 - */ - @Override - public void deleteUser(Long id) { - userDao.deleteById(id); - } - - /** - * 更新用户 - * - * @param user 用户 - * @return 更新后的用户 - */ - @Override - public User updateUser(User user) { - if (ObjectUtil.isNull(user)) { - throw new RuntimeException("用户id不能为null"); - } - userDao.updateTemplateById(user); - return userDao.single(user.getId()); - } - - /** - * 查询单个用户 - * - * @param id 主键id - * @return 用户信息 - */ - @Override - public User getUser(Long id) { - return userDao.single(id); - } - - /** - * 查询用户列表 - * - * @return 用户列表 - */ - @Override - public List getUserList() { - return userDao.all(); - } - - /** - * 分页查询 - * - * @param currentPage 当前页 - * @param pageSize 每页条数 - * @return 分页用户列表 - */ - @Override - public PageQuery getUserByPage(Integer currentPage, Integer pageSize) { - return userDao.createLambdaQuery().page(currentPage, pageSize); - } -} diff --git a/spring-boot-demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java b/spring-boot-demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java deleted file mode 100644 index 0f3aaa2..0000000 --- a/spring-boot-demo-orm-beetlsql/src/test/java/com/xkcoding/orm/beetlsql/service/UserServiceTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.xkcoding.orm.beetlsql.service; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.json.JSONUtil; -import com.xkcoding.orm.beetlsql.SpringBootDemoOrmBeetlsqlApplicationTests; -import com.xkcoding.orm.beetlsql.entity.User; -import com.xkcoding.orm.beetlsql.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.util.Lists; -import org.beetl.sql.core.engine.PageQuery; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -/** - *

    - * User Service测试 - *

    - * - * @package: com.xkcoding.orm.beetlsql.service - * @description: User Service测试 - * @author: yangkai.shen - * @date: Created in 2018/11/14 16:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoOrmBeetlsqlApplicationTests { - @Autowired - private UserService userService; - - @Test - public void saveUser() { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - - user = userService.saveUser(user); - Assert.assertTrue(ObjectUtil.isNotNull(user.getId())); - log.debug("【user】= {}", user); - } - - @Test - public void saveUserList() { - List users = Lists.newArrayList(); - for (int i = 5; i < 15; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - users.add(user); - } - userService.saveUserList(users); - Assert.assertTrue(userService.getUserList().size() > 2); - } - - @Test - public void deleteUser() { - userService.deleteUser(1L); - User user = userService.getUser(1L); - Assert.assertTrue(ObjectUtil.isNull(user)); - } - - @Test - public void updateUser() { - User user = userService.getUser(2L); - user.setName("beetlSql 修改后的名字"); - User update = userService.updateUser(user); - Assert.assertEquals("beetlSql 修改后的名字", update.getName()); - log.debug("【update】= {}", update); - } - - @Test - public void getUser() { - User user = userService.getUser(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - @Test - public void getUserList() { - List userList = userService.getUserList(); - Assert.assertTrue(CollUtil.isNotEmpty(userList)); - log.debug("【userList】= {}", userList); - } - - @Test - public void getUserByPage() { - List userList = userService.getUserList(); - PageQuery userByPage = userService.getUserByPage(1, 5); - Assert.assertEquals(5, userByPage.getList().size()); - Assert.assertEquals(userList.size(), userByPage.getTotalRow()); - log.debug("【userByPage】= {}", JSONUtil.toJsonStr(userByPage)); - } -} \ No newline at end of file diff --git a/spring-boot-demo-orm-jdbctemplate/README.md b/spring-boot-demo-orm-jdbctemplate/README.md deleted file mode 100644 index 3d54e67..0000000 --- a/spring-boot-demo-orm-jdbctemplate/README.md +++ /dev/null @@ -1,332 +0,0 @@ -# spring-boot-demo-orm-jdbctemplate -> 本 demo 主要演示了Spring Boot如何使用 JdbcTemplate 操作数据库,并且简易地封装了一个通用的 Dao 层,包括增删改查。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-jdbctemplate - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-jdbctemplate - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-jdbctemplate - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## BaseDao.java - -```java -/** - *

    - * Dao基类 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.dao.base - * @description: Dao基类 - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:28 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class BaseDao { - private JdbcTemplate jdbcTemplate; - private Class clazz; - - @SuppressWarnings(value = "unchecked") - public BaseDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - } - - /** - * 通用插入,自增列需要添加 {@link Pk} 注解 - * - * @param t 对象 - * @param ignoreNull 是否忽略 null 值 - * @return 操作的行数 - */ - protected Integer insert(T t, Boolean ignoreNull) { - String table = getTableName(t); - - List filterField = getField(t, ignoreNull); - - List columnList = getColumns(filterField); - - String columns = StrUtil.join(Const.SEPARATOR_COMMA, columnList); - - // 构造占位符 - String params = StrUtil.repeatAndJoin("?", columnList.size(), Const.SEPARATOR_COMMA); - - // 构造值 - Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); - - String sql = StrUtil.format("INSERT INTO {table} ({columns}) VALUES ({params})", Dict.create().set("table", table).set("columns", columns).set("params", params)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - return jdbcTemplate.update(sql, values); - } - - /** - * 通用根据主键删除 - * - * @param pk 主键 - * @return 影响行数 - */ - protected Integer deleteById(P pk) { - String tableName = getTableName(); - String sql = StrUtil.format("DELETE FROM {table} where id = ?", Dict.create().set("table", tableName)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); - return jdbcTemplate.update(sql, pk); - } - - /** - * 通用根据主键更新,自增列需要添加 {@link Pk} 注解 - * - * @param t 对象 - * @param pk 主键 - * @param ignoreNull 是否忽略 null 值 - * @return 操作的行数 - */ - protected Integer updateById(T t, P pk, Boolean ignoreNull) { - String tableName = getTableName(t); - - List filterField = getField(t, ignoreNull); - - List columnList = getColumns(filterField); - - List columns = columnList.stream().map(s -> StrUtil.appendIfMissing(s, " = ?")).collect(Collectors.toList()); - String params = StrUtil.join(Const.SEPARATOR_COMMA, columns); - - // 构造值 - List valueList = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).collect(Collectors.toList()); - valueList.add(pk); - - Object[] values = ArrayUtil.toArray(valueList, Object.class); - - String sql = StrUtil.format("UPDATE {table} SET {params} where id = ?", Dict.create().set("table", tableName).set("params", params)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - return jdbcTemplate.update(sql, values); - } - - /** - * 通用根据主键查询单条记录 - * - * @param pk 主键 - * @return 单条记录 - */ - public T findOneById(P pk) { - String tableName = getTableName(); - String sql = StrUtil.format("SELECT * FROM {table} where id = ?", Dict.create().set("table", tableName)); - RowMapper rowMapper = new BeanPropertyRowMapper<>(clazz); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); - return jdbcTemplate.queryForObject(sql, new Object[]{pk}, rowMapper); - } - - /** - * 根据对象查询 - * - * @param t 查询条件 - * @return 对象列表 - */ - public List findByExample(T t) { - String tableName = getTableName(t); - List filterField = getField(t, true); - List columnList = getColumns(filterField); - - List columns = columnList.stream().map(s -> " and " + s + " = ? ").collect(Collectors.toList()); - - String where = StrUtil.join(" ", columns); - // 构造值 - Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); - - String sql = StrUtil.format("SELECT * FROM {table} where 1=1 {where}", Dict.create().set("table", tableName).set("where", StrUtil.isBlank(where) ? "" : where)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - List> maps = jdbcTemplate.queryForList(sql, values); - List ret = CollUtil.newArrayList(); - maps.forEach(map -> ret.add(BeanUtil.fillBeanWithMap(map, ReflectUtil.newInstance(clazz), true, false))); - return ret; - } - - /** - * 获取表名 - * - * @param t 对象 - * @return 表名 - */ - private String getTableName(T t) { - Table tableAnnotation = t.getClass().getAnnotation(Table.class); - if (ObjectUtil.isNotNull(tableAnnotation)) { - return StrUtil.format("`{}`", tableAnnotation.name()); - } else { - return StrUtil.format("`{}`", t.getClass().getName().toLowerCase()); - } - } - - /** - * 获取表名 - * - * @return 表名 - */ - private String getTableName() { - Table tableAnnotation = clazz.getAnnotation(Table.class); - if (ObjectUtil.isNotNull(tableAnnotation)) { - return StrUtil.format("`{}`", tableAnnotation.name()); - } else { - return StrUtil.format("`{}`", clazz.getName().toLowerCase()); - } - } - - /** - * 获取列 - * - * @param fieldList 字段列表 - * @return 列信息列表 - */ - private List getColumns(List fieldList) { - // 构造列 - List columnList = CollUtil.newArrayList(); - for (Field field : fieldList) { - Column columnAnnotation = field.getAnnotation(Column.class); - String columnName; - if (ObjectUtil.isNotNull(columnAnnotation)) { - columnName = columnAnnotation.name(); - } else { - columnName = field.getName(); - } - columnList.add(StrUtil.format("`{}`", columnName)); - } - return columnList; - } - - /** - * 获取字段列表 {@code 过滤数据库中不存在的字段,以及自增列} - * - * @param t 对象 - * @param ignoreNull 是否忽略空值 - * @return 字段列表 - */ - private List getField(T t, Boolean ignoreNull) { - // 获取所有字段,包含父类中的字段 - Field[] fields = ReflectUtil.getFields(t.getClass()); - - // 过滤数据库中不存在的字段,以及自增列 - List filterField; - Stream fieldStream = CollUtil.toList(fields).stream().filter(field -> ObjectUtil.isNull(field.getAnnotation(Ignore.class)) || ObjectUtil.isNull(field.getAnnotation(Pk.class))); - - // 是否过滤字段值为null的字段 - if (ignoreNull) { - filterField = fieldStream.filter(field -> ObjectUtil.isNotNull(ReflectUtil.getFieldValue(t, field))).collect(Collectors.toList()); - } else { - filterField = fieldStream.collect(Collectors.toList()); - } - return filterField; - } - -} -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - initialization-mode: always - continue-on-error: true - schema: - - "classpath:db/schema.sql" - data: - - "classpath:db/data.sql" - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SpringBootDemoHikariCP - max-lifetime: 60000 - connection-timeout: 30000 -logging: - level: - com.xkcoding: debug -``` - -## 备注 - -其余详细代码参见 demo \ No newline at end of file diff --git a/spring-boot-demo-orm-jdbctemplate/pom.xml b/spring-boot-demo-orm-jdbctemplate/pom.xml deleted file mode 100644 index 8d31e9b..0000000 --- a/spring-boot-demo-orm-jdbctemplate/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-jdbctemplate - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-jdbctemplate - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-jdbctemplate - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java deleted file mode 100644 index ab0d2c5..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/SpringBootDemoOrmJdbctemplateApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.orm.jdbctemplate; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/15 9:50 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoOrmJdbctemplateApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmJdbctemplateApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java deleted file mode 100644 index 4dd5245..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Column.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

    - * 列注解 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.annotation - * @description: 列注解 - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:23 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) -public @interface Column { - /** - * 列名 - * - * @return 列名 - */ - String name(); -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java deleted file mode 100644 index 7a84f3d..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Ignore.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

    - * 需要忽略的字段 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.annotation - * @description: 需要忽略的字段 - * @author: yangkai.shen - * @date: Created in 2018/10/15 1:25 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) -public @interface Ignore { -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java deleted file mode 100644 index aea8366..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Pk.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

    - * 主键注解 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.annotation - * @description: 主键注解 - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:23 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) -public @interface Pk { - /** - * 自增 - * - * @return 自增主键 - */ - boolean auto() default true; -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java deleted file mode 100644 index b509548..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/annotation/Table.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

    - * 表注解 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.annotation - * @description: 表注解 - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:23 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface Table { - /** - * 表名 - * - * @return 表名 - */ - String name(); -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java deleted file mode 100644 index 2156ea5..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/constant/Const.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.constant; - -/** - *

    - * 常量池 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.constant - * @description: 常量池 - * @author: yangkai.shen - * @date: Created in 2018/10/15 10:59 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface Const { - /** - * 加密盐前缀 - */ - String SALT_PREFIX = "::SpringBootDemo::"; - - /** - * 逗号分隔符 - */ - String SEPARATOR_COMMA = ","; -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java deleted file mode 100644 index 4616ff3..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/controller/UserController.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.controller; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.orm.jdbctemplate.entity.User; -import com.xkcoding.orm.jdbctemplate.service.IUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - *

    - * User Controller - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.controller - * @description: User Controller - * @author: yangkai.shen - * @date: Created in 2018/10/15 1:58 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@Slf4j -public class UserController { - private final IUserService userService; - - @Autowired - public UserController(IUserService userService) { - this.userService = userService; - } - - @PostMapping("/user") - public Dict save(@RequestBody User user) { - Boolean save = userService.save(user); - return Dict.create().set("code", save ? 200 : 500).set("msg", save ? "成功" : "失败").set("data", save ? user : null); - } - - @DeleteMapping("/user/{id}") - public Dict delete(@PathVariable Long id) { - Boolean delete = userService.delete(id); - return Dict.create().set("code", delete ? 200 : 500).set("msg", delete ? "成功" : "失败"); - } - - @PutMapping("/user/{id}") - public Dict update(@RequestBody User user, @PathVariable Long id) { - Boolean update = userService.update(user, id); - return Dict.create().set("code", update ? 200 : 500).set("msg", update ? "成功" : "失败").set("data", update ? user : null); - } - - @GetMapping("/user/{id}") - public Dict getUser(@PathVariable Long id) { - User user = userService.getUser(id); - return Dict.create().set("code", 200).set("msg", "成功").set("data", user); - } - - @GetMapping("/user") - public Dict getUser(User user) { - List userList = userService.getUser(user); - return Dict.create().set("code", 200).set("msg", "成功").set("data", userList); - } -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java deleted file mode 100644 index 0ccf990..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/UserDao.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.dao; - -import com.xkcoding.orm.jdbctemplate.dao.base.BaseDao; -import com.xkcoding.orm.jdbctemplate.entity.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - *

    - * User Dao - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.dao - * @description: User Dao - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:15 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Repository -public class UserDao extends BaseDao { - - @Autowired - public UserDao(JdbcTemplate jdbcTemplate) { - super(jdbcTemplate); - } - - /** - * 保存用户 - * - * @param user 用户对象 - * @return 操作影响行数 - */ - public Integer insert(User user) { - return super.insert(user, true); - } - - /** - * 根据主键删除用户 - * - * @param id 主键id - * @return 操作影响行数 - */ - public Integer delete(Long id) { - return super.deleteById(id); - } - - /** - * 更新用户 - * - * @param user 用户对象 - * @param id 主键id - * @return 操作影响行数 - */ - public Integer update(User user, Long id) { - return super.updateById(user, id, true); - } - - /** - * 根据主键获取用户 - * - * @param id 主键id - * @return id对应的用户 - */ - public User selectById(Long id) { - return super.findOneById(id); - } - - /** - * 根据查询条件获取用户列表 - * - * @param user 用户查询条件 - * @return 用户列表 - */ - public List selectUserList(User user) { - return super.findByExample(user); - } -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java deleted file mode 100644 index 42c735d..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/dao/base/BaseDao.java +++ /dev/null @@ -1,240 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.dao.base; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.xkcoding.orm.jdbctemplate.annotation.Column; -import com.xkcoding.orm.jdbctemplate.annotation.Ignore; -import com.xkcoding.orm.jdbctemplate.annotation.Pk; -import com.xkcoding.orm.jdbctemplate.annotation.Table; -import com.xkcoding.orm.jdbctemplate.constant.Const; -import lombok.extern.slf4j.Slf4j; -import org.springframework.jdbc.core.BeanPropertyRowMapper; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - *

    - * Dao基类 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.dao.base - * @description: Dao基类 - * @author: yangkai.shen - * @date: Created in 2018/10/15 11:28 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class BaseDao { - private JdbcTemplate jdbcTemplate; - private Class clazz; - - @SuppressWarnings(value = "unchecked") - public BaseDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - } - - /** - * 通用插入,自增列需要添加 {@link Pk} 注解 - * - * @param t 对象 - * @param ignoreNull 是否忽略 null 值 - * @return 操作的行数 - */ - protected Integer insert(T t, Boolean ignoreNull) { - String table = getTableName(t); - - List filterField = getField(t, ignoreNull); - - List columnList = getColumns(filterField); - - String columns = StrUtil.join(Const.SEPARATOR_COMMA, columnList); - - // 构造占位符 - String params = StrUtil.repeatAndJoin("?", columnList.size(), Const.SEPARATOR_COMMA); - - // 构造值 - Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); - - String sql = StrUtil.format("INSERT INTO {table} ({columns}) VALUES ({params})", Dict.create().set("table", table).set("columns", columns).set("params", params)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - return jdbcTemplate.update(sql, values); - } - - /** - * 通用根据主键删除 - * - * @param pk 主键 - * @return 影响行数 - */ - protected Integer deleteById(P pk) { - String tableName = getTableName(); - String sql = StrUtil.format("DELETE FROM {table} where id = ?", Dict.create().set("table", tableName)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); - return jdbcTemplate.update(sql, pk); - } - - /** - * 通用根据主键更新,自增列需要添加 {@link Pk} 注解 - * - * @param t 对象 - * @param pk 主键 - * @param ignoreNull 是否忽略 null 值 - * @return 操作的行数 - */ - protected Integer updateById(T t, P pk, Boolean ignoreNull) { - String tableName = getTableName(t); - - List filterField = getField(t, ignoreNull); - - List columnList = getColumns(filterField); - - List columns = columnList.stream().map(s -> StrUtil.appendIfMissing(s, " = ?")).collect(Collectors.toList()); - String params = StrUtil.join(Const.SEPARATOR_COMMA, columns); - - // 构造值 - List valueList = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).collect(Collectors.toList()); - valueList.add(pk); - - Object[] values = ArrayUtil.toArray(valueList, Object.class); - - String sql = StrUtil.format("UPDATE {table} SET {params} where id = ?", Dict.create().set("table", tableName).set("params", params)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - return jdbcTemplate.update(sql, values); - } - - /** - * 通用根据主键查询单条记录 - * - * @param pk 主键 - * @return 单条记录 - */ - public T findOneById(P pk) { - String tableName = getTableName(); - String sql = StrUtil.format("SELECT * FROM {table} where id = ?", Dict.create().set("table", tableName)); - RowMapper rowMapper = new BeanPropertyRowMapper<>(clazz); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(pk)); - return jdbcTemplate.queryForObject(sql, new Object[]{pk}, rowMapper); - } - - /** - * 根据对象查询 - * - * @param t 查询条件 - * @return 对象列表 - */ - public List findByExample(T t) { - String tableName = getTableName(t); - List filterField = getField(t, true); - List columnList = getColumns(filterField); - - List columns = columnList.stream().map(s -> " and " + s + " = ? ").collect(Collectors.toList()); - - String where = StrUtil.join(" ", columns); - // 构造值 - Object[] values = filterField.stream().map(field -> ReflectUtil.getFieldValue(t, field)).toArray(); - - String sql = StrUtil.format("SELECT * FROM {table} where 1=1 {where}", Dict.create().set("table", tableName).set("where", StrUtil.isBlank(where) ? "" : where)); - log.debug("【执行SQL】SQL:{}", sql); - log.debug("【执行SQL】参数:{}", JSONUtil.toJsonStr(values)); - List> maps = jdbcTemplate.queryForList(sql, values); - List ret = CollUtil.newArrayList(); - maps.forEach(map -> ret.add(BeanUtil.fillBeanWithMap(map, ReflectUtil.newInstance(clazz), true, false))); - return ret; - } - - /** - * 获取表名 - * - * @param t 对象 - * @return 表名 - */ - private String getTableName(T t) { - Table tableAnnotation = t.getClass().getAnnotation(Table.class); - if (ObjectUtil.isNotNull(tableAnnotation)) { - return StrUtil.format("`{}`", tableAnnotation.name()); - } else { - return StrUtil.format("`{}`", t.getClass().getName().toLowerCase()); - } - } - - /** - * 获取表名 - * - * @return 表名 - */ - private String getTableName() { - Table tableAnnotation = clazz.getAnnotation(Table.class); - if (ObjectUtil.isNotNull(tableAnnotation)) { - return StrUtil.format("`{}`", tableAnnotation.name()); - } else { - return StrUtil.format("`{}`", clazz.getName().toLowerCase()); - } - } - - /** - * 获取列 - * - * @param fieldList 字段列表 - * @return 列信息列表 - */ - private List getColumns(List fieldList) { - // 构造列 - List columnList = CollUtil.newArrayList(); - for (Field field : fieldList) { - Column columnAnnotation = field.getAnnotation(Column.class); - String columnName; - if (ObjectUtil.isNotNull(columnAnnotation)) { - columnName = columnAnnotation.name(); - } else { - columnName = field.getName(); - } - columnList.add(StrUtil.format("`{}`", columnName)); - } - return columnList; - } - - /** - * 获取字段列表 {@code 过滤数据库中不存在的字段,以及自增列} - * - * @param t 对象 - * @param ignoreNull 是否忽略空值 - * @return 字段列表 - */ - private List getField(T t, Boolean ignoreNull) { - // 获取所有字段,包含父类中的字段 - Field[] fields = ReflectUtil.getFields(t.getClass()); - - // 过滤数据库中不存在的字段,以及自增列 - List filterField; - Stream fieldStream = CollUtil.toList(fields).stream().filter(field -> ObjectUtil.isNull(field.getAnnotation(Ignore.class)) || ObjectUtil.isNull(field.getAnnotation(Pk.class))); - - // 是否过滤字段值为null的字段 - if (ignoreNull) { - filterField = fieldStream.filter(field -> ObjectUtil.isNotNull(ReflectUtil.getFieldValue(t, field))).collect(Collectors.toList()); - } else { - filterField = fieldStream.collect(Collectors.toList()); - } - return filterField; - } - -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java deleted file mode 100644 index e21697b..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/entity/User.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.entity; - -import com.xkcoding.orm.jdbctemplate.annotation.Pk; -import com.xkcoding.orm.jdbctemplate.annotation.Column; -import com.xkcoding.orm.jdbctemplate.annotation.Table; -import lombok.Data; - -import java.io.Serializable; -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/10/15 10:45 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Table(name = "orm_user") -public class User implements Serializable { - /** - * 主键 - */ - @Pk - private Long id; - - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - @Column(name = "phone_number") - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 创建时间 - */ - @Column(name = "create_time") - private Date createTime; - - /** - * 上次登录时间 - */ - @Column(name = "last_login_time") - private Date lastLoginTime; - - /** - * 上次更新时间 - */ - @Column(name = "last_update_time") - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java deleted file mode 100644 index c4b686e..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/IUserService.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.service; - -import com.xkcoding.orm.jdbctemplate.entity.User; - -import java.util.List; - -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.service - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/10/15 1:51 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface IUserService { - /** - * 保存用户 - * - * @param user 用户实体 - * @return 保存成功 {@code true} 保存失败 {@code false} - */ - Boolean save(User user); - - /** - * 删除用户 - * - * @param id 主键id - * @return 删除成功 {@code true} 删除失败 {@code false} - */ - Boolean delete(Long id); - - /** - * 更新用户 - * - * @param user 用户实体 - * @param id 主键id - * @return 更新成功 {@code true} 更新失败 {@code false} - */ - Boolean update(User user, Long id); - - /** - * 获取单个用户 - * - * @param id 主键id - * @return 单个用户对象 - */ - User getUser(Long id); - - /** - * 获取用户列表 - * - * @param user 用户实体 - * @return 用户列表 - */ - List getUser(User user); - -} diff --git a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java b/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java deleted file mode 100644 index 6f0dccf..0000000 --- a/spring-boot-demo-orm-jdbctemplate/src/main/java/com/xkcoding/orm/jdbctemplate/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.xkcoding.orm.jdbctemplate.service.impl; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.bean.copier.CopyOptions; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import com.xkcoding.orm.jdbctemplate.constant.Const; -import com.xkcoding.orm.jdbctemplate.dao.UserDao; -import com.xkcoding.orm.jdbctemplate.entity.User; -import com.xkcoding.orm.jdbctemplate.service.IUserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - *

    - * User Service Implement - *

    - * - * @package: com.xkcoding.orm.jdbctemplate.service.impl - * @description: User Service Implement - * @author: yangkai.shen - * @date: Created in 2018/10/15 1:53 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class UserServiceImpl implements IUserService { - private final UserDao userDao; - - @Autowired - public UserServiceImpl(UserDao userDao) { - this.userDao = userDao; - } - - /** - * 保存用户 - * - * @param user 用户实体 - * @return 保存成功 {@code true} 保存失败 {@code false} - */ - @Override - public Boolean save(User user) { - String rawPass = user.getPassword(); - String salt = IdUtil.simpleUUID(); - String pass = SecureUtil.md5(rawPass + Const.SALT_PREFIX + salt); - user.setPassword(pass); - user.setSalt(salt); - return userDao.insert(user) > 0; - } - - /** - * 删除用户 - * - * @param id 主键id - * @return 删除成功 {@code true} 删除失败 {@code false} - */ - @Override - public Boolean delete(Long id) { - return userDao.delete(id) > 0; - } - - /** - * 更新用户 - * - * @param user 用户实体 - * @param id 主键id - * @return 更新成功 {@code true} 更新失败 {@code false} - */ - @Override - public Boolean update(User user, Long id) { - User exist = getUser(id); - if (StrUtil.isNotBlank(user.getPassword())) { - String rawPass = user.getPassword(); - String salt = IdUtil.simpleUUID(); - String pass = SecureUtil.md5(rawPass + Const.SALT_PREFIX + salt); - user.setPassword(pass); - user.setSalt(salt); - } - BeanUtil.copyProperties(user, exist, CopyOptions.create().setIgnoreNullValue(true)); - exist.setLastUpdateTime(new DateTime()); - return userDao.update(exist, id) > 0; - } - - /** - * 获取单个用户 - * - * @param id 主键id - * @return 单个用户对象 - */ - @Override - public User getUser(Long id) { - return userDao.findOneById(id); - } - - /** - * 获取用户列表 - * - * @param user 用户实体 - * @return 用户列表 - */ - @Override - public List getUser(User user) { - return userDao.findByExample(user); - } -} diff --git a/spring-boot-demo-orm-jpa/README.md b/spring-boot-demo-orm-jpa/README.md deleted file mode 100644 index fd30136..0000000 --- a/spring-boot-demo-orm-jpa/README.md +++ /dev/null @@ -1,590 +0,0 @@ -# spring-boot-demo-orm-jpa -> 此 demo 主要演示了 Spring Boot 如何使用 JPA 操作数据库,包含简单使用以及级联使用。 - -## 主要代码 - -### pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-jpa - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-jpa - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter - - - - mysql - mysql-connector-java - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-jpa - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` -### JpaConfig.java -```java -/** - *

    - * JPA配置类 - *

    - * - * @package: com.xkcoding.orm.jpa.config - * @description: JPA配置类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 11:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaAuditing -@EnableJpaRepositories(basePackages = "com.xkcoding.orm.jpa.repository", transactionManagerRef = "jpaTransactionManager") -public class JpaConfig { - @Bean - @ConfigurationProperties(prefix = "spring.datasource") - public DataSource dataSource() { - return DataSourceBuilder.create().build(); - } - - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - HibernateJpaVendorAdapter japVendor = new HibernateJpaVendorAdapter(); - japVendor.setGenerateDdl(false); - LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); - entityManagerFactory.setDataSource(dataSource()); - entityManagerFactory.setJpaVendorAdapter(japVendor); - entityManagerFactory.setPackagesToScan("com.xkcoding.orm.jpa.entity"); - return entityManagerFactory; - } - - @Bean - public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager transactionManager = new JpaTransactionManager(); - transactionManager.setEntityManagerFactory(entityManagerFactory); - return transactionManager; - } -} -``` -### User.java -```java -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:06 - * @copyright: Copyright (c) - * @version: V1.0 - * @modified: 76peter - */ -@EqualsAndHashCode(callSuper = true) -@NoArgsConstructor -@AllArgsConstructor -@Data -@Builder -@Entity -@Table(name = "orm_user") -@ToString(callSuper = true) -public class User extends AbstractAuditModel { - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - @Column(name = "phone_number") - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 上次登录时间 - */ - @Column(name = "last_login_time") - private Date lastLoginTime; - - /** - * 关联部门表 - * 1、关系维护端,负责多对多关系的绑定和解除 - * 2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User) - * 3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Department) - * 4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名, - * 即表名为user_department - * 关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id,这里使用referencedColumnName指定 - * 关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,department_id - * 主表就是关系维护端对应的表,从表就是关系被维护端对应的表 - */ - @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinTable(name = "orm_user_dept", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "dept_id", referencedColumnName = "id")) - private Collection departmentList; - -} -``` -### Department.java -```java -/** - *

    - * 部门实体类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity - * @description: 部门实体类 - * @author: 76peter - * @date: Created in 2019/10/1 18:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: 76peter - */ -@EqualsAndHashCode(callSuper = true) -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Entity -@Table(name = "orm_department") -@ToString(callSuper = true) -public class Department extends AbstractAuditModel { - - /** - * 部门名 - */ - @Column(name = "name", columnDefinition = "varchar(255) not null") - private String name; - - /** - * 上级部门id - */ - @ManyToOne(cascade = {CascadeType.REFRESH}, optional = true) - @JoinColumn(name = "superior", referencedColumnName = "id") - private Department superior; - /** - * 所属层级 - */ - @Column(name = "levels", columnDefinition = "int not null default 0") - private Integer levels; - /** - * 排序 - */ - @Column(name = "order_no", columnDefinition = "int not null default 0") - private Integer orderNo; - /** - * 子部门集合 - */ - @OneToMany(cascade = {CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER, mappedBy = "superior") - private Collection children; - - /** - * 部门下用户集合 - */ - @ManyToMany(mappedBy = "departmentList") - private Collection userList; - -} -``` -### AbstractAuditModel.java -```java -/** - *

    - * 实体通用父类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity.base - * @description: 实体通用父类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:01 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -@Data -public abstract class AbstractAuditModel implements Serializable { - /** - * 主键 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 创建时间 - */ - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "create_time", nullable = false, updatable = false) - @CreatedDate - private Date createTime; - - /** - * 上次更新时间 - */ - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "last_update_time", nullable = false) - @LastModifiedDate - private Date lastUpdateTime; -} -``` -### UserDao.java -```java -/** - *

    - * User Dao - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: User Dao - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:07 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Repository -public interface UserDao extends JpaRepository { - -} -``` -### DepartmentDao.java -```java -/** - *

    - * User Dao - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: Department Dao - * @author: 76peter - * @date: Created in 2019/10/1 18:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: 76peter - */ -@Repository -public interface DepartmentDao extends JpaRepository { - /** - * 根据层级查询部门 - * - * @param level 层级 - * @return 部门列表 - */ - List findDepartmentsByLevels(Integer level); -} -``` -### application.yml -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - datasource: - jdbc-url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - initialization-mode: always - continue-on-error: true - schema: - - "classpath:db/schema.sql" - data: - - "classpath:db/data.sql" - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SpringBootDemoHikariCP - max-lifetime: 60000 - connection-timeout: 30000 - jpa: - show-sql: true - hibernate: - ddl-auto: validate - properties: - hibernate: - dialect: org.hibernate.dialect.MySQL57InnoDBDialect - open-in-view: true -logging: - level: - com.xkcoding: debug - org.hibernate.SQL: debug - org.hibernate.type: trace -``` -### UserDaoTest.java -```java -/** - *

    - * jpa 测试类 - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: jpa 测试类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserDaoTest extends SpringBootDemoOrmJpaApplicationTests { - @Autowired - private UserDao userDao; - - /** - * 测试保存 - */ - @Test - public void testSave() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); - userDao.save(testSave3); - - Assert.assertNotNull(testSave3.getId()); - Optional byId = userDao.findById(testSave3.getId()); - Assert.assertTrue(byId.isPresent()); - log.debug("【byId】= {}", byId.get()); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - long count = userDao.count(); - userDao.deleteById(1L); - long left = userDao.count(); - Assert.assertEquals(count - 1, left); - } - - /** - * 测试修改 - */ - @Test - public void testUpdate() { - userDao.findById(1L).ifPresent(user -> { - user.setName("JPA修改名字"); - userDao.save(user); - }); - Assert.assertEquals("JPA修改名字", userDao.findById(1L).get().getName()); - } - - /** - * 测试查询单个 - */ - @Test - public void testQueryOne() { - Optional byId = userDao.findById(1L); - Assert.assertTrue(byId.isPresent()); - log.debug("【byId】= {}", byId.get()); - } - - /** - * 测试查询所有 - */ - @Test - public void testQueryAll() { - List users = userDao.findAll(); - Assert.assertNotEquals(0, users.size()); - log.debug("【users】= {}", users); - } - - /** - * 测试分页排序查询 - */ - @Test - public void testQueryPage() { - // 初始化数据 - initData(); - // JPA分页的时候起始页是页码减1 - Integer currentPage = 0; - Integer pageSize = 5; - Sort sort = Sort.by(Sort.Direction.DESC, "id"); - PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort); - Page userPage = userDao.findAll(pageRequest); - - Assert.assertEquals(5, userPage.getSize()); - Assert.assertEquals(userDao.count(), userPage.getTotalElements()); - log.debug("【id】= {}", userPage.getContent().stream().map(User::getId).collect(Collectors.toList())); - } - - /** - * 初始化10条数据 - */ - private void initData() { - List userList = Lists.newArrayList(); - for (int i = 0; i < 10; i++) { - String salt = IdUtil.fastSimpleUUID(); - int index = 3 + i; - User user = User.builder().name("testSave" + index).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + index + "@xkcoding.com").phoneNumber("1730000000" + index).status(1).lastLoginTime(new DateTime()).build(); - userList.add(user); - } - userDao.saveAll(userList); - } - -} -``` -### DepartmentDaoTest.java -```java -/** - *

    - * jpa 测试类 - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: jpa 测试类 - * @author: 76peter - * @date: Created in 2018/11/7 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: 76peter - */ -@Slf4j -public class DepartmentDaoTest extends SpringBootDemoOrmJpaApplicationTests { - @Autowired - private DepartmentDao departmentDao; - @Autowired - private UserDao userDao; - - /** - * 测试保存 ,根节点 - */ - @Test - @Transactional - public void testSave() { - Collection departmentList = departmentDao.findDepartmentsByLevels(0); - - if (departmentList.size() == 0) { - Department testSave1 = Department.builder().name("testSave1").orderNo(0).levels(0).superior(null).build(); - Department testSave1_1 = Department.builder().name("testSave1_1").orderNo(0).levels(1).superior(testSave1).build(); - Department testSave1_2 = Department.builder().name("testSave1_2").orderNo(0).levels(1).superior(testSave1).build(); - Department testSave1_1_1 = Department.builder().name("testSave1_1_1").orderNo(0).levels(2).superior(testSave1_1).build(); - departmentList.add(testSave1); - departmentList.add(testSave1_1); - departmentList.add(testSave1_2); - departmentList.add(testSave1_1_1); - departmentDao.saveAll(departmentList); - - Collection deptall = departmentDao.findAll(); - log.debug("【部门】= {}", JSONArray.toJSONString((List) deptall)); - } - - - userDao.findById(1L).ifPresent(user -> { - user.setName("添加部门"); - Department dept = departmentDao.findById(2L).get(); - user.setDepartmentList(departmentList); - userDao.save(user); - }); - - log.debug("用户部门={}", JSONUtil.toJsonStr(userDao.findById(1L).get().getDepartmentList())); - - - departmentDao.findById(2L).ifPresent(dept -> { - Collection userlist = dept.getUserList(); - //关联关系由user维护中间表,department userlist不会发生变化,可以增加查询方法来处理 重写getUserList方法 - log.debug("部门下用户={}", JSONUtil.toJsonStr(userlist)); - }); - - - userDao.findById(1L).ifPresent(user -> { - user.setName("清空部门"); - user.setDepartmentList(null); - userDao.save(user); - }); - log.debug("用户部门={}", userDao.findById(1L).get().getDepartmentList()); - - } -} -``` - -### 其余代码及 SQL 参见本 demo - -## 参考 - -- Spring Data JPA 官方文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/ diff --git a/spring-boot-demo-orm-jpa/pom.xml b/spring-boot-demo-orm-jpa/pom.xml deleted file mode 100644 index 2bd654a..0000000 --- a/spring-boot-demo-orm-jpa/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-jpa - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-jpa - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter - - - - mysql - mysql-connector-java - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-jpa - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java deleted file mode 100644 index b048ea0..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/SpringBootDemoOrmJpaApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.orm.jpa; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.orm.jpa - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/28 22:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoOrmJpaApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmJpaApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java deleted file mode 100644 index c606943..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/config/JpaConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.xkcoding.orm.jpa.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -/** - *

    - * JPA配置类 - *

    - * - * @package: com.xkcoding.orm.jpa.config - * @description: JPA配置类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 11:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableTransactionManagement -@EnableJpaAuditing -@EnableJpaRepositories(basePackages = "com.xkcoding.orm.jpa.repository", transactionManagerRef = "jpaTransactionManager") -public class JpaConfig { - @Bean - @ConfigurationProperties(prefix = "spring.datasource") - public DataSource dataSource() { - return DataSourceBuilder.create().build(); - } - - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - HibernateJpaVendorAdapter japVendor = new HibernateJpaVendorAdapter(); - japVendor.setGenerateDdl(false); - LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); - entityManagerFactory.setDataSource(dataSource()); - entityManagerFactory.setJpaVendorAdapter(japVendor); - entityManagerFactory.setPackagesToScan("com.xkcoding.orm.jpa.entity"); - return entityManagerFactory; - } - - @Bean - public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager transactionManager = new JpaTransactionManager(); - transactionManager.setEntityManagerFactory(entityManagerFactory); - return transactionManager; - } -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java deleted file mode 100644 index 3b71bec..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/Department.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.xkcoding.orm.jpa.entity; - -import com.xkcoding.orm.jpa.entity.base.AbstractAuditModel; -import lombok.*; - -import javax.persistence.*; -import java.util.Collection; - -/** - *

    - * 部门实体类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity - * @description: 部门实体类 - * @author: 76peter - * @date: Created in 2019/10/1 18:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: 76peter - */ -@EqualsAndHashCode(callSuper = true) -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Entity -@Table(name = "orm_department") -@ToString(callSuper = true) -public class Department extends AbstractAuditModel { - - /** - * 部门名 - */ - @Column(name = "name", columnDefinition = "varchar(255) not null") - private String name; - - /** - * 上级部门id - */ - @ManyToOne(cascade = {CascadeType.REFRESH}, optional = true) - @JoinColumn(name = "superior", referencedColumnName = "id") - private Department superior; - /** - * 所属层级 - */ - @Column(name = "levels", columnDefinition = "int not null default 0") - private Integer levels; - /** - * 排序 - */ - @Column(name = "order_no", columnDefinition = "int not null default 0") - private Integer orderNo; - /** - * 子部门集合 - */ - @OneToMany(cascade = {CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER, mappedBy = "superior") - private Collection children; - - /** - * 部门下用户集合 - */ - @ManyToMany(mappedBy = "departmentList") - private Collection userList; - -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java deleted file mode 100644 index 27fb30a..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/User.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.xkcoding.orm.jpa.entity; - -import com.xkcoding.orm.jpa.entity.base.AbstractAuditModel; -import lombok.*; - -import javax.persistence.*; -import java.util.Collection; -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:06 - * @copyright: Copyright (c) - * @version: V1.0 - * @modified: 76peter - */ -@EqualsAndHashCode(callSuper = true) -@NoArgsConstructor -@AllArgsConstructor -@Data -@Builder -@Entity -@Table(name = "orm_user") -@ToString(callSuper = true) -public class User extends AbstractAuditModel { - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - @Column(name = "phone_number") - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 上次登录时间 - */ - @Column(name = "last_login_time") - private Date lastLoginTime; - - /** - * 关联部门表 - * 1、关系维护端,负责多对多关系的绑定和解除 - * 2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User) - * 3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Department) - * 4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名, - * 即表名为user_department - * 关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id,这里使用referencedColumnName指定 - * 关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,department_id - * 主表就是关系维护端对应的表,从表就是关系被维护端对应的表 - */ - @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinTable(name = "orm_user_dept", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "dept_id", referencedColumnName = "id")) - private Collection departmentList; - -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java deleted file mode 100644 index beb8547..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/entity/base/AbstractAuditModel.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.xkcoding.orm.jpa.entity.base; - -import lombok.Data; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import javax.persistence.*; -import java.io.Serializable; -import java.util.Date; - -/** - *

    - * 实体通用父类 - *

    - * - * @package: com.xkcoding.orm.jpa.entity.base - * @description: 实体通用父类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:01 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -@Data -public abstract class AbstractAuditModel implements Serializable { - /** - * 主键 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /** - * 创建时间 - */ - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "create_time", nullable = false, updatable = false) - @CreatedDate - private Date createTime; - - /** - * 上次更新时间 - */ - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "last_update_time", nullable = false) - @LastModifiedDate - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java deleted file mode 100644 index dc3fab4..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/DepartmentDao.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.orm.jpa.repository; - -import com.xkcoding.orm.jpa.entity.Department; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - - -/** - *

    - * User Dao - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: Department Dao - * @author: 76peter - * @date: Created in 2019/10/1 18:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: 76peter - */ -@Repository -public interface DepartmentDao extends JpaRepository { - /** - * 根据层级查询部门 - * - * @param level 层级 - * @return 部门列表 - */ - List findDepartmentsByLevels(Integer level); -} diff --git a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java b/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java deleted file mode 100644 index 383719a..0000000 --- a/spring-boot-demo-orm-jpa/src/main/java/com/xkcoding/orm/jpa/repository/UserDao.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xkcoding.orm.jpa.repository; - -import com.xkcoding.orm.jpa.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - *

    - * User Dao - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: User Dao - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:07 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Repository -public interface UserDao extends JpaRepository { - -} diff --git a/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java b/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java deleted file mode 100644 index 054a5bf..0000000 --- a/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/DepartmentDaoTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.xkcoding.orm.jpa.repository; - -import cn.hutool.json.JSONUtil; -import com.xkcoding.orm.jpa.SpringBootDemoOrmJpaApplicationTests; -import com.xkcoding.orm.jpa.entity.Department; -import com.xkcoding.orm.jpa.entity.User; -import lombok.extern.slf4j.Slf4j; -import net.minidev.json.JSONArray; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.transaction.Transactional; -import java.util.Collection; -import java.util.List; - -/** - *

    - * jpa 测试类 - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: jpa 测试类 - * @author: 76peter - * @date: Created in 2018/11/7 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: 76peter - */ -@Slf4j -public class DepartmentDaoTest extends SpringBootDemoOrmJpaApplicationTests { - @Autowired - private DepartmentDao departmentDao; - @Autowired - private UserDao userDao; - - /** - * 测试保存 ,根节点 - */ - @Test - @Transactional - public void testSave() { - Collection departmentList = departmentDao.findDepartmentsByLevels(0); - - if (departmentList.size() == 0) { - Department testSave1 = Department.builder().name("testSave1").orderNo(0).levels(0).superior(null).build(); - Department testSave1_1 = Department.builder().name("testSave1_1").orderNo(0).levels(1).superior(testSave1).build(); - Department testSave1_2 = Department.builder().name("testSave1_2").orderNo(0).levels(1).superior(testSave1).build(); - Department testSave1_1_1 = Department.builder().name("testSave1_1_1").orderNo(0).levels(2).superior(testSave1_1).build(); - departmentList.add(testSave1); - departmentList.add(testSave1_1); - departmentList.add(testSave1_2); - departmentList.add(testSave1_1_1); - departmentDao.saveAll(departmentList); - - Collection deptall = departmentDao.findAll(); - log.debug("【部门】= {}", JSONArray.toJSONString((List) deptall)); - } - - - userDao.findById(1L).ifPresent(user -> { - user.setName("添加部门"); - Department dept = departmentDao.findById(2L).get(); - user.setDepartmentList(departmentList); - userDao.save(user); - }); - - log.debug("用户部门={}", JSONUtil.toJsonStr(userDao.findById(1L).get().getDepartmentList())); - - - departmentDao.findById(2L).ifPresent(dept -> { - Collection userlist = dept.getUserList(); - //关联关系由user维护中间表,department userlist不会发生变化,可以增加查询方法来处理 重写getUserList方法 - log.debug("部门下用户={}", JSONUtil.toJsonStr(userlist)); - }); - - - userDao.findById(1L).ifPresent(user -> { - user.setName("清空部门"); - user.setDepartmentList(null); - userDao.save(user); - }); - log.debug("用户部门={}", userDao.findById(1L).get().getDepartmentList()); - - } - -} diff --git a/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java b/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java deleted file mode 100644 index edde196..0000000 --- a/spring-boot-demo-orm-jpa/src/test/java/com/xkcoding/orm/jpa/repository/UserDaoTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.xkcoding.orm.jpa.repository; - -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import com.xkcoding.orm.jpa.SpringBootDemoOrmJpaApplicationTests; -import com.xkcoding.orm.jpa.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.util.Lists; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - *

    - * jpa 测试类 - *

    - * - * @package: com.xkcoding.orm.jpa.repository - * @description: jpa 测试类 - * @author: yangkai.shen - * @date: Created in 2018/11/7 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserDaoTest extends SpringBootDemoOrmJpaApplicationTests { - @Autowired - private UserDao userDao; - - /** - * 测试保存 - */ - @Test - public void testSave() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); - userDao.save(testSave3); - - Assert.assertNotNull(testSave3.getId()); - Optional byId = userDao.findById(testSave3.getId()); - Assert.assertTrue(byId.isPresent()); - log.debug("【byId】= {}", byId.get()); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - long count = userDao.count(); - userDao.deleteById(1L); - long left = userDao.count(); - Assert.assertEquals(count - 1, left); - } - - /** - * 测试修改 - */ - @Test - public void testUpdate() { - userDao.findById(1L).ifPresent(user -> { - user.setName("JPA修改名字"); - userDao.save(user); - }); - Assert.assertEquals("JPA修改名字", userDao.findById(1L).get().getName()); - } - - /** - * 测试查询单个 - */ - @Test - public void testQueryOne() { - Optional byId = userDao.findById(1L); - Assert.assertTrue(byId.isPresent()); - log.debug("【byId】= {}", byId.get()); - } - - /** - * 测试查询所有 - */ - @Test - public void testQueryAll() { - List users = userDao.findAll(); - Assert.assertNotEquals(0, users.size()); - log.debug("【users】= {}", users); - } - - /** - * 测试分页排序查询 - */ - @Test - public void testQueryPage() { - // 初始化数据 - initData(); - // JPA分页的时候起始页是页码减1 - Integer currentPage = 0; - Integer pageSize = 5; - Sort sort = Sort.by(Sort.Direction.DESC, "id"); - PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort); - Page userPage = userDao.findAll(pageRequest); - - Assert.assertEquals(5, userPage.getSize()); - Assert.assertEquals(userDao.count(), userPage.getTotalElements()); - log.debug("【id】= {}", userPage.getContent().stream().map(User::getId).collect(Collectors.toList())); - } - - /** - * 初始化10条数据 - */ - private void initData() { - List userList = Lists.newArrayList(); - for (int i = 0; i < 10; i++) { - String salt = IdUtil.fastSimpleUUID(); - int index = 3 + i; - User user = User.builder().name("testSave" + index).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + index + "@xkcoding.com").phoneNumber("1730000000" + index).status(1).lastLoginTime(new DateTime()).build(); - userList.add(user); - } - userDao.saveAll(userList); - } - -} \ No newline at end of file diff --git a/spring-boot-demo-orm-mybatis-mapper-page/README.md b/spring-boot-demo-orm-mybatis-mapper-page/README.md deleted file mode 100644 index ee3dcb7..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/README.md +++ /dev/null @@ -1,346 +0,0 @@ -# spring-boot-demo-orm-mybatis-mapper-page - -> 此 demo 演示了 Spring Boot 如何集成通用Mapper插件和分页助手插件,简化Mybatis开发,带给你难以置信的开发体验。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-mybatis-mapper-page - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis-mapper-page - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.0.4 - 1.2.9 - - - - - org.springframework.boot - spring-boot-starter - - - - - tk.mybatis - mapper-spring-boot-starter - ${mybatis.mapper.version} - - - - - com.github.pagehelper - pagehelper-spring-boot-starter - ${mybatis.pagehelper.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - mysql - mysql-connector-java - - - - - spring-boot-demo-orm-mybatis-mapper-page - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoOrmMybatisApplication.java - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/8 13:43 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.MapperAndPage.mapper"}) // 注意:这里的 MapperScan 是 tk.mybatis.spring.annotation.MapperScan 这个包下的 -public class SpringBootDemoOrmMybatisMapperPageApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmMybatisMapperPageApplication.class, args); - } -} -``` - -## application.yml - -```yaml -spring: - datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - initialization-mode: always - continue-on-error: true - schema: - - "classpath:db/schema.sql" - data: - - "classpath:db/data.sql" - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SpringBootDemoHikariCP - max-lifetime: 60000 - connection-timeout: 30000 -logging: - level: - com.xkcoding: debug - com.xkcoding.orm.mybatis.MapperAndPage.mapper: trace -mybatis: - configuration: - # 下划线转驼峰 - map-underscore-to-camel-case: true - mapper-locations: classpath:mappers/*.xml - type-aliases-package: com.xkcoding.orm.mybatis.MapperAndPage.entity -mapper: - mappers: - - tk.mybatis.mapper.common.Mapper - not-empty: true - style: camelhump - wrap-keyword: "`{0}`" - safe-delete: true - safe-update: true - identity: MYSQL -pagehelper: - auto-dialect: true - helper-dialect: mysql - reasonable: true - params: count=countSql -``` - -## UserMapper.java - -```java -/** - *

    - * UserMapper - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage.mapper - * @description: UserMapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 14:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -// 注意:这里的Mapper是tk.mybatis.mapper.common.Mapper包下的 -public interface UserMapper extends Mapper, MySqlMapper { -} -``` - -## UserMapperTest.java - -```java -/** - *

    - * UserMapper 测试 - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage.mapper - * @description: UserMapper 测试 - * @author: yangkai.shen - * @date: Created in 2018/11/8 14:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserMapperTest extends SpringBootDemoOrmMybatisMapperPageApplicationTests { - - @Autowired - private UserMapper userMapper; - - /** - * 测试通用Mapper - 保存 - */ - @Test - public void testInsert() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - userMapper.insertUseGeneratedKeys(testSave3); - Assert.assertNotNull(testSave3.getId()); - log.debug("【测试主键回写#testSave3.getId()】= {}", testSave3.getId()); - } - - /** - * 测试通用Mapper - 批量保存 - */ - @Test - public void testInsertList() { - List userList = Lists.newArrayList(); - for (int i = 4; i < 14; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - userList.add(user); - } - int i = userMapper.insertList(userList); - Assert.assertEquals(userList.size(), i); - List ids = userList.stream().map(User::getId).collect(Collectors.toList()); - log.debug("【测试主键回写#userList.ids】= {}", ids); - } - - /** - * 测试通用Mapper - 删除 - */ - @Test - public void testDelete() { - Long primaryKey = 1L; - int i = userMapper.deleteByPrimaryKey(primaryKey); - Assert.assertEquals(1, i); - User user = userMapper.selectByPrimaryKey(primaryKey); - Assert.assertNull(user); - } - - /** - * 测试通用Mapper - 更新 - */ - @Test - public void testUpdate() { - Long primaryKey = 1L; - User user = userMapper.selectByPrimaryKey(primaryKey); - user.setName("通用Mapper名字更新"); - int i = userMapper.updateByPrimaryKeySelective(user); - Assert.assertEquals(1, i); - User update = userMapper.selectByPrimaryKey(primaryKey); - Assert.assertNotNull(update); - Assert.assertEquals("通用Mapper名字更新", update.getName()); - log.debug("【update】= {}", update); - } - - /** - * 测试通用Mapper - 查询单个 - */ - @Test - public void testQueryOne(){ - User user = userMapper.selectByPrimaryKey(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - /** - * 测试通用Mapper - 查询全部 - */ - @Test - public void testQueryAll() { - List users = userMapper.selectAll(); - Assert.assertTrue(CollUtil.isNotEmpty(users)); - log.debug("【users】= {}", users); - } - - /** - * 测试分页助手 - 分页排序查询 - */ - @Test - public void testQueryByPageAndSort() { - initData(); - int currentPage = 1; - int pageSize = 5; - String orderBy = "id desc"; - int count = userMapper.selectCount(null); - PageHelper.startPage(currentPage, pageSize, orderBy); - List users = userMapper.selectAll(); - PageInfo userPageInfo = new PageInfo<>(users); - Assert.assertEquals(5, userPageInfo.getSize()); - Assert.assertEquals(count, userPageInfo.getTotal()); - log.debug("【userPageInfo】= {}", userPageInfo); - } - - /** - * 测试通用Mapper - 条件查询 - */ - @Test - public void testQueryByCondition() { - initData(); - Example example = new Example(User.class); - // 过滤 - example.createCriteria().andLike("name", "%Save1%").orEqualTo("phoneNumber", "17300000001"); - // 排序 - example.setOrderByClause("id desc"); - int count = userMapper.selectCountByExample(example); - // 分页 - PageHelper.startPage(1, 3); - // 查询 - List userList = userMapper.selectByExample(example); - PageInfo userPageInfo = new PageInfo<>(userList); - Assert.assertEquals(3, userPageInfo.getSize()); - Assert.assertEquals(count, userPageInfo.getTotal()); - log.debug("【userPageInfo】= {}", userPageInfo); - } - - /** - * 初始化数据 - */ - private void initData() { - testInsertList(); - } - -} -``` - -## 参考 - -- 通用Mapper官方文档:https://github.com/abel533/Mapper/wiki/1.integration -- pagehelper 官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md \ No newline at end of file diff --git a/spring-boot-demo-orm-mybatis-mapper-page/pom.xml b/spring-boot-demo-orm-mybatis-mapper-page/pom.xml deleted file mode 100644 index cf8e12a..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-mybatis-mapper-page - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis-mapper-page - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.0.4 - 1.2.9 - - - - - org.springframework.boot - spring-boot-starter - - - - - tk.mybatis - mapper-spring-boot-starter - ${mybatis.mapper.version} - - - - - com.github.pagehelper - pagehelper-spring-boot-starter - ${mybatis.pagehelper.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - mysql - mysql-connector-java - - - - - spring-boot-demo-orm-mybatis-mapper-page - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java b/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java deleted file mode 100644 index 41e1887..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/SpringBootDemoOrmMybatisMapperPageApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.orm.mybatis.MapperAndPage; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import tk.mybatis.spring.annotation.MapperScan; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/8 13:43 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.MapperAndPage.mapper"}) -public class SpringBootDemoOrmMybatisMapperPageApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmMybatisMapperPageApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java b/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java deleted file mode 100644 index 07ecca1..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/entity/User.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.xkcoding.orm.mybatis.MapperAndPage.entity; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import tk.mybatis.mapper.annotation.KeySql; - -import javax.persistence.Id; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 14:14 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Table(name = "orm_user") -public class User implements Serializable { - private static final long serialVersionUID = -1840831686851699943L; - - /** - * 主键 - */ - @Id - @KeySql(useGeneratedKeys = true) - private Long id; - - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 上次登录时间 - */ - private Date lastLoginTime; - - /** - * 上次更新时间 - */ - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java b/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java deleted file mode 100644 index 4cc51ae..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/src/main/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xkcoding.orm.mybatis.MapperAndPage.mapper; - -import com.xkcoding.orm.mybatis.MapperAndPage.entity.User; -import org.springframework.stereotype.Component; -import tk.mybatis.mapper.common.Mapper; -import tk.mybatis.mapper.common.MySqlMapper; - -/** - *

    - * UserMapper - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage.mapper - * @description: UserMapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 14:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface UserMapper extends Mapper, MySqlMapper { -} diff --git a/spring-boot-demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java b/spring-boot-demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java deleted file mode 100644 index 409616a..0000000 --- a/spring-boot-demo-orm-mybatis-mapper-page/src/test/java/com/xkcoding/orm/mybatis/MapperAndPage/mapper/UserMapperTest.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.xkcoding.orm.mybatis.MapperAndPage.mapper; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; -import com.xkcoding.orm.mybatis.MapperAndPage.SpringBootDemoOrmMybatisMapperPageApplicationTests; -import com.xkcoding.orm.mybatis.MapperAndPage.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.util.Lists; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import tk.mybatis.mapper.entity.Example; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * UserMapper 测试 - *

    - * - * @package: com.xkcoding.orm.mybatis.MapperAndPage.mapper - * @description: UserMapper 测试 - * @author: yangkai.shen - * @date: Created in 2018/11/8 14:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserMapperTest extends SpringBootDemoOrmMybatisMapperPageApplicationTests { - - @Autowired - private UserMapper userMapper; - - /** - * 测试通用Mapper - 保存 - */ - @Test - public void testInsert() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - userMapper.insertUseGeneratedKeys(testSave3); - Assert.assertNotNull(testSave3.getId()); - log.debug("【测试主键回写#testSave3.getId()】= {}", testSave3.getId()); - } - - /** - * 测试通用Mapper - 批量保存 - */ - @Test - public void testInsertList() { - List userList = Lists.newArrayList(); - for (int i = 4; i < 14; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - userList.add(user); - } - int i = userMapper.insertList(userList); - Assert.assertEquals(userList.size(), i); - List ids = userList.stream().map(User::getId).collect(Collectors.toList()); - log.debug("【测试主键回写#userList.ids】= {}", ids); - } - - /** - * 测试通用Mapper - 删除 - */ - @Test - public void testDelete() { - Long primaryKey = 1L; - int i = userMapper.deleteByPrimaryKey(primaryKey); - Assert.assertEquals(1, i); - User user = userMapper.selectByPrimaryKey(primaryKey); - Assert.assertNull(user); - } - - /** - * 测试通用Mapper - 更新 - */ - @Test - public void testUpdate() { - Long primaryKey = 1L; - User user = userMapper.selectByPrimaryKey(primaryKey); - user.setName("通用Mapper名字更新"); - int i = userMapper.updateByPrimaryKeySelective(user); - Assert.assertEquals(1, i); - User update = userMapper.selectByPrimaryKey(primaryKey); - Assert.assertNotNull(update); - Assert.assertEquals("通用Mapper名字更新", update.getName()); - log.debug("【update】= {}", update); - } - - /** - * 测试通用Mapper - 查询单个 - */ - @Test - public void testQueryOne(){ - User user = userMapper.selectByPrimaryKey(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - /** - * 测试通用Mapper - 查询全部 - */ - @Test - public void testQueryAll() { - List users = userMapper.selectAll(); - Assert.assertTrue(CollUtil.isNotEmpty(users)); - log.debug("【users】= {}", users); - } - - /** - * 测试分页助手 - 分页排序查询 - */ - @Test - public void testQueryByPageAndSort() { - initData(); - int currentPage = 1; - int pageSize = 5; - String orderBy = "id desc"; - int count = userMapper.selectCount(null); - PageHelper.startPage(currentPage, pageSize, orderBy); - List users = userMapper.selectAll(); - PageInfo userPageInfo = new PageInfo<>(users); - Assert.assertEquals(5, userPageInfo.getSize()); - Assert.assertEquals(count, userPageInfo.getTotal()); - log.debug("【userPageInfo】= {}", userPageInfo); - } - - /** - * 测试通用Mapper - 条件查询 - */ - @Test - public void testQueryByCondition() { - initData(); - Example example = new Example(User.class); - // 过滤 - example.createCriteria().andLike("name", "%Save1%").orEqualTo("phoneNumber", "17300000001"); - // 排序 - example.setOrderByClause("id desc"); - int count = userMapper.selectCountByExample(example); - // 分页 - PageHelper.startPage(1, 3); - // 查询 - List userList = userMapper.selectByExample(example); - PageInfo userPageInfo = new PageInfo<>(userList); - Assert.assertEquals(3, userPageInfo.getSize()); - Assert.assertEquals(count, userPageInfo.getTotal()); - log.debug("【userPageInfo】= {}", userPageInfo); - } - - /** - * 初始化数据 - */ - private void initData() { - testInsertList(); - } - -} \ No newline at end of file diff --git a/spring-boot-demo-orm-mybatis-plus/README.md b/spring-boot-demo-orm-mybatis-plus/README.md deleted file mode 100644 index 658fd20..0000000 --- a/spring-boot-demo-orm-mybatis-plus/README.md +++ /dev/null @@ -1,543 +0,0 @@ -# spring-boot-demo-orm-mybatis-plus - -> 此 demo 演示了 Spring Boot 如何集成 mybatis-plus,简化Mybatis开发,带给你难以置信的开发体验。 -> -> - 2019-09-14 新增:ActiveRecord 模式操作 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-mybatis-plus - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis-plus - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.0.5 - - - - - org.springframework.boot - spring-boot-starter - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis.plus.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-mybatis-plus - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## MybatisPlusConfig.java - -```java -/** - *

    - * mybatis-plus 配置 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.config - * @description: mybatis-plus 配置 - * @author: yangkai.shen - * @date: Created in 2018/11/8 17:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.plus.mapper"}) -@EnableTransactionManagement -public class MybatisPlusConfig { - /** - * 性能分析拦截器,不建议生产使用 - */ - @Bean - public PerformanceInterceptor performanceInterceptor(){ - return new PerformanceInterceptor(); - } - - /** - * 分页插件 - */ - @Bean - public PaginationInterceptor paginationInterceptor() { - return new PaginationInterceptor(); - } -} -``` - -## CommonFieldHandler.java - -```java -package com.xkcoding.orm.mybatis.plus.config; - -import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.reflection.MetaObject; -import org.springframework.stereotype.Component; - -import java.util.Date; - -/** - *

    - * 通用字段填充 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.config - * @description: 通用字段填充 - * @author: yangkai.shen - * @date: Created in 2018/11/8 17:40 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Component -public class CommonFieldHandler implements MetaObjectHandler { - - @Override - public void insertFill(MetaObject metaObject) { - log.info("start insert fill ...."); - this.setFieldValByName("createTime", new Date(), metaObject); - this.setFieldValByName("lastUpdateTime", new Date(), metaObject); - } - - @Override - public void updateFill(MetaObject metaObject) { - log.info("start update fill ...."); - this.setFieldValByName("lastUpdateTime", new Date(), metaObject); - } -} -``` - -## application.yml - -```yaml -spring: - datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - initialization-mode: always - continue-on-error: true - schema: - - "classpath:db/schema.sql" - data: - - "classpath:db/data.sql" - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SpringBootDemoHikariCP - max-lifetime: 60000 - connection-timeout: 30000 -logging: - level: - com.xkcoding: debug - com.xkcoding.orm.mybatis.plus.mapper: trace -mybatis-plus: - mapper-locations: classpath:mappers/*.xml - #实体扫描,多个package用逗号或者分号分隔 - typeAliasesPackage: com.xkcoding.orm.mybatis.plus.entity - global-config: - # 数据库相关配置 - db-config: - #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; - id-type: auto - #字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断" - field-strategy: not_empty - #驼峰下划线转换 - table-underline: true - #是否开启大写命名,默认不开启 - #capital-mode: true - #逻辑删除配置 - #logic-delete-value: 1 - #logic-not-delete-value: 0 - db-type: mysql - #刷新mapper 调试神器 - refresh: true - # 原生配置 - configuration: - map-underscore-to-camel-case: true - cache-enabled: true -``` - -## UserMapper.java - -```java -/** - *

    - * UserMapper - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.mapper - * @description: UserMapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 16:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface UserMapper extends BaseMapper { -} -``` - -## UserService.java - -```java -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService extends IService { -} -``` - -## UserServiceImpl.java - -```java -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service.impl - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class UserServiceImpl extends ServiceImpl implements UserService { -} -``` - -## UserServiceTest.java - -```java -/** - *

    - * User Service 测试 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service - * @description: User Service 测试 - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:13 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoOrmMybatisPlusApplicationTests { - @Autowired - private UserService userService; - - /** - * 测试Mybatis-Plus 新增 - */ - @Test - public void testSave() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); - boolean save = userService.save(testSave3); - Assert.assertTrue(save); - log.debug("【测试id回显#testSave3.getId()】= {}", testSave3.getId()); - } - - /** - * 测试Mybatis-Plus 批量新增 - */ - @Test - public void testSaveList() { - List userList = Lists.newArrayList(); - for (int i = 4; i < 14; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).build(); - userList.add(user); - } - boolean batch = userService.saveBatch(userList); - Assert.assertTrue(batch); - List ids = userList.stream().map(User::getId).collect(Collectors.toList()); - log.debug("【userList#ids】= {}", ids); - } - - /** - * 测试Mybatis-Plus 删除 - */ - @Test - public void testDelete() { - boolean remove = userService.removeById(1L); - Assert.assertTrue(remove); - User byId = userService.getById(1L); - Assert.assertNull(byId); - } - - /** - * 测试Mybatis-Plus 修改 - */ - @Test - public void testUpdate() { - User user = userService.getById(1L); - Assert.assertNotNull(user); - user.setName("MybatisPlus修改名字"); - boolean b = userService.updateById(user); - Assert.assertTrue(b); - User update = userService.getById(1L); - Assert.assertEquals("MybatisPlus修改名字", update.getName()); - log.debug("【update】= {}", update); - } - - /** - * 测试Mybatis-Plus 查询单个 - */ - @Test - public void testQueryOne() { - User user = userService.getById(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - /** - * 测试Mybatis-Plus 查询全部 - */ - @Test - public void testQueryAll() { - List list = userService.list(new QueryWrapper<>()); - Assert.assertTrue(CollUtil.isNotEmpty(list)); - log.debug("【list】= {}", list); - } - - /** - * 测试Mybatis-Plus 分页排序查询 - */ - @Test - public void testQueryByPageAndSort() { - initData(); - int count = userService.count(new QueryWrapper<>()); - Page userPage = new Page<>(1, 5); - userPage.setDesc("id"); - IPage page = userService.page(userPage, new QueryWrapper<>()); - Assert.assertEquals(5, page.getSize()); - Assert.assertEquals(count, page.getTotal()); - log.debug("【page.getRecords()】= {}", page.getRecords()); - } - - /** - * 测试Mybatis-Plus 自定义查询 - */ - @Test - public void testQueryByCondition() { - initData(); - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.like("name", "Save1").or().eq("phone_number", "17300000001").orderByDesc("id"); - int count = userService.count(wrapper); - Page userPage = new Page<>(1, 3); - IPage page = userService.page(userPage, wrapper); - Assert.assertEquals(3, page.getSize()); - Assert.assertEquals(count, page.getTotal()); - log.debug("【page.getRecords()】= {}", page.getRecords()); - } - - /** - * 初始化数据 - */ - private void initData() { - testSaveList(); - } - -} -``` - -## 2019-09-14新增 - -### ActiveRecord 模式 - -- Role.java - -```java -/** - *

    - * 角色实体类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/16 14:04 - */ -@Data -@TableName("orm_role") -@Accessors(chain = true) -@EqualsAndHashCode(callSuper = true) -public class Role extends Model { - /** - * 主键 - */ - private Long id; - - /** - * 角色名 - */ - private String name; - - /** - * 主键值,ActiveRecord 模式这个必须有,否则 xxById 的方法都将失效! - * 即使使用 ActiveRecord 不会用到 RoleMapper,RoleMapper 这个接口也必须创建 - */ - @Override - protected Serializable pkVal() { - - return this.id; - } -} -``` - -- RoleMapper.java - -```java -/** - *

    - * RoleMapper - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/16 14:06 - */ -public interface RoleMapper extends BaseMapper { -} -``` - -- ActiveRecordTest.java - -```java -/** - *

    - * Role - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/16 14:19 - */ -@Slf4j -public class ActiveRecordTest extends SpringBootDemoOrmMybatisPlusApplicationTests { - /** - * 测试 ActiveRecord 插入数据 - */ - @Test - public void testActiveRecordInsert() { - Role role = new Role(); - role.setName("VIP"); - Assert.assertTrue(role.insert()); - // 成功直接拿会写的 ID - log.debug("【role】= {}", role); - } - - /** - * 测试 ActiveRecord 更新数据 - */ - @Test - public void testActiveRecordUpdate() { - Assert.assertTrue(new Role().setId(1L).setName("管理员-1").updateById()); - Assert.assertTrue(new Role().update(new UpdateWrapper().lambda().set(Role::getName, "普通用户-1").eq(Role::getId, 2))); - } - - /** - * 测试 ActiveRecord 查询数据 - */ - @Test - public void testActiveRecordSelect() { - Assert.assertEquals("管理员", new Role().setId(1L).selectById().getName()); - Role role = new Role().selectOne(new QueryWrapper().lambda().eq(Role::getId, 2)); - Assert.assertEquals("普通用户", role.getName()); - List roles = new Role().selectAll(); - Assert.assertTrue(roles.size() > 0); - log.debug("【roles】= {}", roles); - } - - /** - * 测试 ActiveRecord 删除数据 - */ - @Test - public void testActiveRecordDelete() { - Assert.assertTrue(new Role().setId(1L).deleteById()); - Assert.assertTrue(new Role().delete(new QueryWrapper().lambda().eq(Role::getName, "普通用户"))); - } -} -``` - -## 参考 - -- mybatis-plus官方文档:http://mp.baomidou.com/ - diff --git a/spring-boot-demo-orm-mybatis-plus/pom.xml b/spring-boot-demo-orm-mybatis-plus/pom.xml deleted file mode 100644 index e3fd928..0000000 --- a/spring-boot-demo-orm-mybatis-plus/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-mybatis-plus - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis-plus - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.1.0 - - - - - org.springframework.boot - spring-boot-starter - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis.plus.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-orm-mybatis-plus - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java deleted file mode 100644 index 9b908b2..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplication.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.orm.mybatis.plus; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/8 16:48 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoOrmMybatisPlusApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmMybatisPlusApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java deleted file mode 100644 index 77584eb..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/CommonFieldHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.config; - -import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.reflection.MetaObject; -import org.springframework.stereotype.Component; - -import java.util.Date; - -/** - *

    - * 通用字段填充 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.config - * @description: 通用字段填充 - * @author: yangkai.shen - * @date: Created in 2018/11/8 17:40 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Component -public class CommonFieldHandler implements MetaObjectHandler { - - @Override - public void insertFill(MetaObject metaObject) { - log.info("start insert fill ...."); - this.setFieldValByName("createTime", new Date(), metaObject); - this.setFieldValByName("lastUpdateTime", new Date(), metaObject); - } - - @Override - public void updateFill(MetaObject metaObject) { - log.info("start update fill ...."); - this.setFieldValByName("lastUpdateTime", new Date(), metaObject); - } -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java deleted file mode 100644 index 4d6f846..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/config/MybatisPlusConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.config; - -import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; -import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor; -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -/** - *

    - * mybatis-plus 配置 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.config - * @description: mybatis-plus 配置 - * @author: yangkai.shen - * @date: Created in 2018/11/8 17:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.plus.mapper"}) -@EnableTransactionManagement -public class MybatisPlusConfig { - /** - * 性能分析拦截器,不建议生产使用 - */ - @Bean - public PerformanceInterceptor performanceInterceptor(){ - return new PerformanceInterceptor(); - } - - /** - * 分页插件 - */ - @Bean - public PaginationInterceptor paginationInterceptor() { - return new PaginationInterceptor(); - } -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java deleted file mode 100644 index 074fc0e..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/Role.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.entity; - -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.activerecord.Model; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; - -import java.io.Serializable; - -/** - *

    - * 角色实体类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/14 14:04 - */ -@Data -@TableName("orm_role") -@Accessors(chain = true) -@EqualsAndHashCode(callSuper = true) -public class Role extends Model { - /** - * 主键 - */ - private Long id; - - /** - * 角色名 - */ - private String name; - - /** - * 主键值,ActiveRecord 模式这个必须有,否则 xxById 的方法都将失效! - * 即使使用 ActiveRecord 不会用到 RoleMapper,RoleMapper 这个接口也必须创建 - */ - @Override - protected Serializable pkVal() { - - return this.id; - } -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java deleted file mode 100644 index afa560a..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/entity/User.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.Date; - -import static com.baomidou.mybatisplus.annotation.FieldFill.INSERT; -import static com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 16:49 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@TableName("orm_user") -public class User implements Serializable { - private static final long serialVersionUID = -1840831686851699943L; - - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 创建时间 - */ - @TableField(fill = INSERT) - private Date createTime; - - /** - * 上次登录时间 - */ - private Date lastLoginTime; - - /** - * 上次更新时间 - */ - @TableField(fill = INSERT_UPDATE) - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java deleted file mode 100644 index 0240849..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/RoleMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.xkcoding.orm.mybatis.plus.entity.Role; - -/** - *

    - * RoleMapper - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/14 14:06 - */ -public interface RoleMapper extends BaseMapper { -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java deleted file mode 100644 index 4bb44b4..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/mapper/UserMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.xkcoding.orm.mybatis.plus.entity.User; -import org.springframework.stereotype.Component; - -/** - *

    - * UserMapper - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.mapper - * @description: UserMapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 16:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface UserMapper extends BaseMapper { -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java deleted file mode 100644 index bb357bb..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/UserService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import com.xkcoding.orm.mybatis.plus.entity.User; - -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserService extends IService { -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java b/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java deleted file mode 100644 index 5afb5f7..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/main/java/com/xkcoding/orm/mybatis/plus/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.service.impl; - -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.xkcoding.orm.mybatis.plus.entity.User; -import com.xkcoding.orm.mybatis.plus.mapper.UserMapper; -import com.xkcoding.orm.mybatis.plus.service.UserService; -import org.springframework.stereotype.Service; - -/** - *

    - * User Service - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service.impl - * @description: User Service - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class UserServiceImpl extends ServiceImpl implements UserService { -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java b/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java deleted file mode 100644 index 9973d2d..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/SpringBootDemoOrmMybatisPlusApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.orm.mybatis.plus; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoOrmMybatisPlusApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java b/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java deleted file mode 100644 index 426b0ef..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/activerecord/ActiveRecordTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.activerecord; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.xkcoding.orm.mybatis.plus.SpringBootDemoOrmMybatisPlusApplicationTests; -import com.xkcoding.orm.mybatis.plus.entity.Role; -import lombok.extern.slf4j.Slf4j; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; - -/** - *

    - * Role - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/14 14:19 - */ -@Slf4j -public class ActiveRecordTest extends SpringBootDemoOrmMybatisPlusApplicationTests { - /** - * 测试 ActiveRecord 插入数据 - */ - @Test - public void testActiveRecordInsert() { - Role role = new Role(); - role.setName("VIP"); - Assert.assertTrue(role.insert()); - // 成功直接拿会写的 ID - log.debug("【role】= {}", role); - } - - /** - * 测试 ActiveRecord 更新数据 - */ - @Test - public void testActiveRecordUpdate() { - Assert.assertTrue(new Role().setId(1L).setName("管理员-1").updateById()); - Assert.assertTrue(new Role().update(new UpdateWrapper().lambda().set(Role::getName, "普通用户-1").eq(Role::getId, 2))); - } - - /** - * 测试 ActiveRecord 查询数据 - */ - @Test - public void testActiveRecordSelect() { - Assert.assertEquals("管理员", new Role().setId(1L).selectById().getName()); - Role role = new Role().selectOne(new QueryWrapper().lambda().eq(Role::getId, 2)); - Assert.assertEquals("普通用户", role.getName()); - List roles = new Role().selectAll(); - Assert.assertTrue(roles.size() > 0); - log.debug("【roles】= {}", roles); - } - - /** - * 测试 ActiveRecord 删除数据 - */ - @Test - public void testActiveRecordDelete() { - Assert.assertTrue(new Role().setId(1L).deleteById()); - Assert.assertTrue(new Role().delete(new QueryWrapper().lambda().eq(Role::getName, "普通用户"))); - } -} diff --git a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java b/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java deleted file mode 100644 index d1bf23f..0000000 --- a/spring-boot-demo-orm-mybatis-plus/src/test/java/com/xkcoding/orm/mybatis/plus/service/UserServiceTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.xkcoding.orm.mybatis.plus.service; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.xkcoding.orm.mybatis.plus.SpringBootDemoOrmMybatisPlusApplicationTests; -import com.xkcoding.orm.mybatis.plus.entity.User; -import com.xkcoding.orm.mybatis.plus.service.UserService; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.util.Lists; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * User Service 测试 - *

    - * - * @package: com.xkcoding.orm.mybatis.plus.service - * @description: User Service 测试 - * @author: yangkai.shen - * @date: Created in 2018/11/8 18:13 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserServiceTest extends SpringBootDemoOrmMybatisPlusApplicationTests { - @Autowired - private UserService userService; - - /** - * 测试Mybatis-Plus 新增 - */ - @Test - public void testSave() { - String salt = IdUtil.fastSimpleUUID(); - User testSave3 = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).build(); - boolean save = userService.save(testSave3); - Assert.assertTrue(save); - log.debug("【测试id回显#testSave3.getId()】= {}", testSave3.getId()); - } - - /** - * 测试Mybatis-Plus 批量新增 - */ - @Test - public void testSaveList() { - List userList = Lists.newArrayList(); - for (int i = 4; i < 14; i++) { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave" + i).password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave" + i + "@xkcoding.com").phoneNumber("1730000000" + i).status(1).lastLoginTime(new DateTime()).build(); - userList.add(user); - } - boolean batch = userService.saveBatch(userList); - Assert.assertTrue(batch); - List ids = userList.stream().map(User::getId).collect(Collectors.toList()); - log.debug("【userList#ids】= {}", ids); - } - - /** - * 测试Mybatis-Plus 删除 - */ - @Test - public void testDelete() { - boolean remove = userService.removeById(1L); - Assert.assertTrue(remove); - User byId = userService.getById(1L); - Assert.assertNull(byId); - } - - /** - * 测试Mybatis-Plus 修改 - */ - @Test - public void testUpdate() { - User user = userService.getById(1L); - Assert.assertNotNull(user); - user.setName("MybatisPlus修改名字"); - boolean b = userService.updateById(user); - Assert.assertTrue(b); - User update = userService.getById(1L); - Assert.assertEquals("MybatisPlus修改名字", update.getName()); - log.debug("【update】= {}", update); - } - - /** - * 测试Mybatis-Plus 查询单个 - */ - @Test - public void testQueryOne() { - User user = userService.getById(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - /** - * 测试Mybatis-Plus 查询全部 - */ - @Test - public void testQueryAll() { - List list = userService.list(new QueryWrapper<>()); - Assert.assertTrue(CollUtil.isNotEmpty(list)); - log.debug("【list】= {}", list); - } - - /** - * 测试Mybatis-Plus 分页排序查询 - */ - @Test - public void testQueryByPageAndSort() { - initData(); - int count = userService.count(new QueryWrapper<>()); - Page userPage = new Page<>(1, 5); - userPage.setDesc("id"); - IPage page = userService.page(userPage, new QueryWrapper<>()); - Assert.assertEquals(5, page.getSize()); - Assert.assertEquals(count, page.getTotal()); - log.debug("【page.getRecords()】= {}", page.getRecords()); - } - - /** - * 测试Mybatis-Plus 自定义查询 - */ - @Test - public void testQueryByCondition() { - initData(); - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.like("name", "Save1").or().eq("phone_number", "17300000001").orderByDesc("id"); - int count = userService.count(wrapper); - Page userPage = new Page<>(1, 3); - IPage page = userService.page(userPage, wrapper); - Assert.assertEquals(3, page.getSize()); - Assert.assertEquals(count, page.getTotal()); - log.debug("【page.getRecords()】= {}", page.getRecords()); - } - - /** - * 初始化数据 - */ - private void initData() { - testSaveList(); - } - -} \ No newline at end of file diff --git a/spring-boot-demo-orm-mybatis/README.md b/spring-boot-demo-orm-mybatis/README.md deleted file mode 100644 index c6d6ab5..0000000 --- a/spring-boot-demo-orm-mybatis/README.md +++ /dev/null @@ -1,293 +0,0 @@ -# spring-boot-demo-orm-mybatis - -> 此 demo 演示了 Spring Boot 如何与原生的 mybatis 整合,使用了 mybatis 官方提供的脚手架 `mybatis-spring-boot-starter `可以很容易的和 Spring Boot 整合。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-orm-mybatis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.3.2 - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - ${mybatis.version} - - - - mysql - mysql-connector-java - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-orm-mybatis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoOrmMybatisApplication.java - -```java -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.orm.mybatis - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 10:52 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.mapper"}) -@SpringBootApplication -public class SpringBootDemoOrmMybatisApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmMybatisApplication.class, args); - } -} -``` - -## application.yml - -```yaml -spring: - datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - type: com.zaxxer.hikari.HikariDataSource - initialization-mode: always - continue-on-error: true - schema: - - "classpath:db/schema.sql" - data: - - "classpath:db/data.sql" - hikari: - minimum-idle: 5 - connection-test-query: SELECT 1 FROM DUAL - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - pool-name: SpringBootDemoHikariCP - max-lifetime: 60000 - connection-timeout: 30000 -logging: - level: - com.xkcoding: debug - com.xkcoding.orm.mybatis.mapper: trace -mybatis: - configuration: - # 下划线转驼峰 - map-underscore-to-camel-case: true - mapper-locations: classpath:mappers/*.xml - type-aliases-package: com.xkcoding.orm.mybatis.entity -``` - -## UserMapper.java - -```java -/** - *

    - * User Mapper - *

    - * - * @package: com.xkcoding.orm.mybatis.mapper - * @description: User Mapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 10:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Mapper -@Component -public interface UserMapper { - - /** - * 查询所有用户 - * - * @return 用户列表 - */ - @Select("SELECT * FROM orm_user") - List selectAllUser(); - - /** - * 根据id查询用户 - * - * @param id 主键id - * @return 当前id的用户,不存在则是 {@code null} - */ - @Select("SELECT * FROM orm_user WHERE id = #{id}") - User selectUserById(@Param("id") Long id); - - /** - * 保存用户 - * - * @param user 用户 - * @return 成功 - {@code 1} 失败 - {@code 0} - */ - int saveUser(@Param("user") User user); - - /** - * 删除用户 - * - * @param id 主键id - * @return 成功 - {@code 1} 失败 - {@code 0} - */ - int deleteById(@Param("id") Long id); - -} -``` - -## UserMapper.xml - -```xml - - - - - - INSERT INTO `orm_user` (`name`, - `password`, - `salt`, - `email`, - `phone_number`, - `status`, - `create_time`, - `last_login_time`, - `last_update_time`) - VALUES (#{user.name}, - #{user.password}, - #{user.salt}, - #{user.email}, - #{user.phoneNumber}, - #{user.status}, - #{user.createTime}, - #{user.lastLoginTime}, - #{user.lastUpdateTime}) - - - - DELETE - FROM `orm_user` - WHERE `id` = #{id} - - -``` - -## UserMapperTest.java - -```java -/** - *

    - * UserMapper 测试类 - *

    - * - * @package: com.xkcoding.orm.mybatis.mapper - * @description: UserMapper 测试类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 11:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserMapperTest extends SpringBootDemoOrmMybatisApplicationTests { - @Autowired - private UserMapper userMapper; - - @Test - public void selectAllUser() { - List userList = userMapper.selectAllUser(); - Assert.assertTrue(CollUtil.isNotEmpty(userList)); - log.debug("【userList】= {}", userList); - } - - @Test - public void selectUserById() { - User user = userMapper.selectUserById(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - @Test - public void saveUser() { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - int i = userMapper.saveUser(user); - Assert.assertEquals(1, i); - } - - @Test - public void deleteById() { - int i = userMapper.deleteById(1L); - Assert.assertEquals(1, i); - } -} -``` - -## 参考 - -- Mybatis官方文档:http://www.mybatis.org/mybatis-3/zh/index.html - -- Mybatis官方脚手架文档:http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ - -- Mybatis整合Spring Boot官方demo:https://github.com/mybatis/spring-boot-starter/tree/master/mybatis-spring-boot-samples diff --git a/spring-boot-demo-orm-mybatis/pom.xml b/spring-boot-demo-orm-mybatis/pom.xml deleted file mode 100644 index 48eb617..0000000 --- a/spring-boot-demo-orm-mybatis/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-orm-mybatis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-orm-mybatis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.3.2 - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - ${mybatis.version} - - - - mysql - mysql-connector-java - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-orm-mybatis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java b/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java deleted file mode 100644 index 27e2ba0..0000000 --- a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/SpringBootDemoOrmMybatisApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.orm.mybatis; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.orm.mybatis - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 10:52 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@MapperScan(basePackages = {"com.xkcoding.orm.mybatis.mapper"}) -@SpringBootApplication -public class SpringBootDemoOrmMybatisApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoOrmMybatisApplication.class, args); - } -} diff --git a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java b/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java deleted file mode 100644 index 21c9d88..0000000 --- a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/entity/User.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.xkcoding.orm.mybatis.entity; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.Date; - -/** - *

    - * 用户实体类 - *

    - * - * @package: com.xkcoding.orm.mybatis.entity - * @description: 用户实体类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 10:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class User implements Serializable { - private static final long serialVersionUID = -1840831686851699943L; - - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String name; - - /** - * 加密后的密码 - */ - private String password; - - /** - * 加密使用的盐 - */ - private String salt; - - /** - * 邮箱 - */ - private String email; - - /** - * 手机号码 - */ - private String phoneNumber; - - /** - * 状态,-1:逻辑删除,0:禁用,1:启用 - */ - private Integer status; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 上次登录时间 - */ - private Date lastLoginTime; - - /** - * 上次更新时间 - */ - private Date lastUpdateTime; -} diff --git a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java b/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java deleted file mode 100644 index b76ac32..0000000 --- a/spring-boot-demo-orm-mybatis/src/main/java/com/xkcoding/orm/mybatis/mapper/UserMapper.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.xkcoding.orm.mybatis.mapper; - -import com.xkcoding.orm.mybatis.entity.User; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - *

    - * User Mapper - *

    - * - * @package: com.xkcoding.orm.mybatis.mapper - * @description: User Mapper - * @author: yangkai.shen - * @date: Created in 2018/11/8 10:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Mapper -@Component -public interface UserMapper { - - /** - * 查询所有用户 - * - * @return 用户列表 - */ - @Select("SELECT * FROM orm_user") - List selectAllUser(); - - /** - * 根据id查询用户 - * - * @param id 主键id - * @return 当前id的用户,不存在则是 {@code null} - */ - @Select("SELECT * FROM orm_user WHERE id = #{id}") - User selectUserById(@Param("id") Long id); - - /** - * 保存用户 - * - * @param user 用户 - * @return 成功 - {@code 1} 失败 - {@code 0} - */ - int saveUser(@Param("user") User user); - - /** - * 删除用户 - * - * @param id 主键id - * @return 成功 - {@code 1} 失败 - {@code 0} - */ - int deleteById(@Param("id") Long id); - -} diff --git a/spring-boot-demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java b/spring-boot-demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java deleted file mode 100644 index 4d29f02..0000000 --- a/spring-boot-demo-orm-mybatis/src/test/java/com/xkcoding/orm/mybatis/mapper/UserMapperTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.xkcoding.orm.mybatis.mapper; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import com.xkcoding.orm.mybatis.SpringBootDemoOrmMybatisApplicationTests; -import com.xkcoding.orm.mybatis.entity.User; -import lombok.extern.slf4j.Slf4j; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -/** - *

    - * UserMapper 测试类 - *

    - * - * @package: com.xkcoding.orm.mybatis.mapper - * @description: UserMapper 测试类 - * @author: yangkai.shen - * @date: Created in 2018/11/8 11:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserMapperTest extends SpringBootDemoOrmMybatisApplicationTests { - @Autowired - private UserMapper userMapper; - - /** - * 测试查询所有 - */ - @Test - public void selectAllUser() { - List userList = userMapper.selectAllUser(); - Assert.assertTrue(CollUtil.isNotEmpty(userList)); - log.debug("【userList】= {}", userList); - } - - /** - * 测试根据主键查询单个 - */ - @Test - public void selectUserById() { - User user = userMapper.selectUserById(1L); - Assert.assertNotNull(user); - log.debug("【user】= {}", user); - } - - /** - * 测试保存 - */ - @Test - public void saveUser() { - String salt = IdUtil.fastSimpleUUID(); - User user = User.builder().name("testSave3").password(SecureUtil.md5("123456" + salt)).salt(salt).email("testSave3@xkcoding.com").phoneNumber("17300000003").status(1).lastLoginTime(new DateTime()).createTime(new DateTime()).lastUpdateTime(new DateTime()).build(); - int i = userMapper.saveUser(user); - Assert.assertEquals(1, i); - } - - /** - * 测试根据主键删除 - */ - @Test - public void deleteById() { - int i = userMapper.deleteById(1L); - Assert.assertEquals(1, i); - } -} \ No newline at end of file diff --git a/spring-boot-demo-properties/README.md b/spring-boot-demo-properties/README.md deleted file mode 100644 index 17a969b..0000000 --- a/spring-boot-demo-properties/README.md +++ /dev/null @@ -1,206 +0,0 @@ -# spring-boot-demo-properties - -> 本 demo 演示如何获取配置文件的自定义配置,以及如何多环境下的配置文件信息的获取 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-properties - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-properties - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-properties - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## ApplicationProperty.java - -```java -/** - *

    - * 项目配置 - *

    - * - * @package: com.xkcoding.properties.property - * @description: 项目配置 - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:50 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Component -public class ApplicationProperty { - @Value("${application.name}") - private String name; - @Value("${application.version}") - private String version; -} -``` - -## DeveloperProperty.java - -```java -/** - *

    - * 开发人员配置信息 - *

    - * - * @package: com.xkcoding.properties.property - * @description: 开发人员配置信息 - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:51 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@ConfigurationProperties(prefix = "developer") -@Component -public class DeveloperProperty { - private String name; - private String website; - private String qq; - private String phoneNumber; -} -``` - -## PropertyController.java - -```java -/** - *

    - * 测试Controller - *

    - * - * @package: com.xkcoding.properties.controller - * @description: 测试Controller - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:49 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -public class PropertyController { - private final ApplicationProperty applicationProperty; - private final DeveloperProperty developerProperty; - - @Autowired - public PropertyController(ApplicationProperty applicationProperty, DeveloperProperty developerProperty) { - this.applicationProperty = applicationProperty; - this.developerProperty = developerProperty; - } - - @GetMapping("/property") - public Dict index() { - return Dict.create().set("applicationProperty", applicationProperty).set("developerProperty", developerProperty); - } -} -``` - -## additional-spring-configuration-metadata.json - -> 位置: src/main/resources/META-INF/additional-spring-configuration-metadata.json - -```json -{ - "properties": [ - { - "name": "application.name", - "description": "Default value is artifactId in pom.xml.", - "type": "java.lang.String" - }, - { - "name": "application.version", - "description": "Default value is version in pom.xml.", - "type": "java.lang.String" - }, - { - "name": "developer.name", - "description": "The Developer Name.", - "type": "java.lang.String" - }, - { - "name": "developer.website", - "description": "The Developer Website.", - "type": "java.lang.String" - }, - { - "name": "developer.qq", - "description": "The Developer QQ Number.", - "type": "java.lang.String" - }, - { - "name": "developer.phone-number", - "description": "The Developer Phone Number.", - "type": "java.lang.String" - } - ] -} -``` - diff --git a/spring-boot-demo-properties/pom.xml b/spring-boot-demo-properties/pom.xml deleted file mode 100644 index 93210d3..0000000 --- a/spring-boot-demo-properties/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-properties - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-properties - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-properties - - - org.springframework.boot - spring-boot-maven-plugin - - - - - src/main/resources - true - - - - - diff --git a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java b/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java deleted file mode 100644 index 93c0feb..0000000 --- a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/SpringBootDemoPropertiesApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.properties; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.properties - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:48 AM - * @copyright: Copyright (c)2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoPropertiesApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoPropertiesApplication.class, args); - } -} diff --git a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java b/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java deleted file mode 100644 index 5e7b49c..0000000 --- a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/controller/PropertyController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.properties.controller; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.properties.property.ApplicationProperty; -import com.xkcoding.properties.property.DeveloperProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 测试Controller - *

    - * - * @package: com.xkcoding.properties.controller - * @description: 测试Controller - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:49 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -public class PropertyController { - private final ApplicationProperty applicationProperty; - private final DeveloperProperty developerProperty; - - @Autowired - public PropertyController(ApplicationProperty applicationProperty, DeveloperProperty developerProperty) { - this.applicationProperty = applicationProperty; - this.developerProperty = developerProperty; - } - - @GetMapping("/property") - public Dict index() { - return Dict.create().set("applicationProperty", applicationProperty).set("developerProperty", developerProperty); - } -} diff --git a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java b/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java deleted file mode 100644 index 0ae920d..0000000 --- a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/ApplicationProperty.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.properties.property; - -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -/** - *

    - * 项目配置 - *

    - * - * @package: com.xkcoding.properties.property - * @description: 项目配置 - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:50 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Component -public class ApplicationProperty { - @Value("${application.name}") - private String name; - @Value("${application.version}") - private String version; -} diff --git a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java b/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java deleted file mode 100644 index d8c486f..0000000 --- a/spring-boot-demo-properties/src/main/java/com/xkcoding/properties/property/DeveloperProperty.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xkcoding.properties.property; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - *

    - * 开发人员配置信息 - *

    - * - * @package: com.xkcoding.properties.property - * @description: 开发人员配置信息 - * @author: yangkai.shen - * @date: Created in 2018/9/29 10:51 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@ConfigurationProperties(prefix = "developer") -@Component -public class DeveloperProperty { - private String name; - private String website; - private String qq; - private String phoneNumber; -} diff --git a/spring-boot-demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java b/spring-boot-demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java deleted file mode 100644 index 7a325a7..0000000 --- a/spring-boot-demo-properties/src/test/java/com/xkcoding/properties/SpringBootDemoPropertiesApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.properties; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoPropertiesApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-ratelimit-guava/README.md b/spring-boot-demo-ratelimit-guava/README.md deleted file mode 100644 index 1b4e91b..0000000 --- a/spring-boot-demo-ratelimit-guava/README.md +++ /dev/null @@ -1,218 +0,0 @@ -# spring-boot-demo-ratelimit-guava - -> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Guava 的 RateLimiter 实现限流,旨在保护 API 被恶意频繁访问的问题。 - -## 1. 主要代码 - -### 1.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-ratelimit-guava - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ratelimit-guava - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-ratelimit-guava - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 1.2. 定义一个限流注解 `RateLimiter.java` - -> 注意代码里使用了 `AliasFor` 设置一组属性的别名,所以获取注解的时候,需要通过 `Spring` 提供的注解工具类 `AnnotationUtils` 获取,不可以通过 `AOP` 参数注入的方式获取,否则有些属性的值将会设置不进去。 - -```java -/** - *

    - * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:14 - * @see AnnotationUtils - *

    - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface RateLimiter { - int NOT_LIMITED = 0; - - /** - * qps - */ - @AliasFor("qps") double value() default NOT_LIMITED; - - /** - * qps - */ - @AliasFor("value") double qps() default NOT_LIMITED; - - /** - * 超时时长 - */ - int timeout() default 0; - - /** - * 超时时间单位 - */ - TimeUnit timeUnit() default TimeUnit.MILLISECONDS; -} -``` - -### 1.3. 定义一个切面 `RateLimiterAspect.java` - -```java -/** - *

    - * 限流切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:27 - */ -@Slf4j -@Aspect -@Component -public class RateLimiterAspect { - private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>(); - - @Pointcut("@annotation(com.xkcoding.ratelimit.guava.annotation.RateLimiter)") - public void rateLimit() { - - } - - @Around("rateLimit()") - public Object pointcut(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 - RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); - if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) { - double qps = rateLimiter.qps(); - if (RATE_LIMITER_CACHE.get(method.getName()) == null) { - // 初始化 QPS - RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps)); - } - - log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate()); - // 尝试获取令牌 - if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) { - throw new RuntimeException("手速太快了,慢点儿吧~"); - } - } - return point.proceed(); - } -} -``` - -### 1.4. 定义两个API接口用于测试限流 - -```java -/** - *

    - * 测试 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:22 - */ -@Slf4j -@RestController -public class TestController { - - @RateLimiter(value = 1.0, timeout = 300) - @GetMapping("/test1") - public Dict test1() { - log.info("【test1】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } - - @GetMapping("/test2") - public Dict test2() { - log.info("【test2】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); - } -} -``` - -## 2. 测试 - -- test1 接口未被限流的时候 - -image-20190912155209716 - -- test1 接口频繁刷新,触发限流的时候 - -image-20190912155229745 - -- test2 接口不做限流,可以一直刷新 - -image-20190912155146012 - -## 3. 参考 - -- [限流原理解读之guava中的RateLimiter](https://juejin.im/post/5bb48d7b5188255c865e31bc) - -- [使用Guava的RateLimiter做限流](https://my.oschina.net/hanchao/blog/1833612) - diff --git a/spring-boot-demo-ratelimit-guava/pom.xml b/spring-boot-demo-ratelimit-guava/pom.xml deleted file mode 100644 index 439736c..0000000 --- a/spring-boot-demo-ratelimit-guava/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-ratelimit-guava - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ratelimit-guava - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-ratelimit-guava - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java b/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java deleted file mode 100644 index 39564bf..0000000 --- a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/SpringBootDemoRatelimitGuavaApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.ratelimit.guava; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:06 - */ -@SpringBootApplication -public class SpringBootDemoRatelimitGuavaApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoRatelimitGuavaApplication.class, args); - } - -} diff --git a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java b/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java deleted file mode 100644 index 6c35192..0000000 --- a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/annotation/RateLimiter.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.ratelimit.guava.annotation; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.AnnotationUtils; - -import java.lang.annotation.*; -import java.util.concurrent.TimeUnit; - -/** - *

    - * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:14 - * @see AnnotationUtils - *

    - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface RateLimiter { - int NOT_LIMITED = 0; - - /** - * qps - */ - @AliasFor("qps") double value() default NOT_LIMITED; - - /** - * qps - */ - @AliasFor("value") double qps() default NOT_LIMITED; - - /** - * 超时时长 - */ - int timeout() default 0; - - /** - * 超时时间单位 - */ - TimeUnit timeUnit() default TimeUnit.MILLISECONDS; -} diff --git a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java b/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java deleted file mode 100644 index d204a12..0000000 --- a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/aspect/RateLimiterAspect.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.xkcoding.ratelimit.guava.aspect; - -import com.xkcoding.ratelimit.guava.annotation.RateLimiter; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - *

    - * 限流切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:27 - */ -@Slf4j -@Aspect -@Component -public class RateLimiterAspect { - private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>(); - - @Pointcut("@annotation(com.xkcoding.ratelimit.guava.annotation.RateLimiter)") - public void rateLimit() { - - } - - @Around("rateLimit()") - public Object pointcut(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 - RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); - if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) { - double qps = rateLimiter.qps(); - if (RATE_LIMITER_CACHE.get(method.getName()) == null) { - // 初始化 QPS - RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps)); - } - - log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate()); - // 尝试获取令牌 - if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) { - throw new RuntimeException("手速太快了,慢点儿吧~"); - } - } - return point.proceed(); - } -} diff --git a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java b/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java deleted file mode 100644 index 66c3eb6..0000000 --- a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/controller/TestController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.ratelimit.guava.controller; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.ratelimit.guava.annotation.RateLimiter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 测试 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 14:22 - */ -@Slf4j -@RestController -public class TestController { - - @RateLimiter(value = 1.0, timeout = 300) - @GetMapping("/test1") - public Dict test1() { - log.info("【test1】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } - - @GetMapping("/test2") - public Dict test2() { - log.info("【test2】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); - } - - @RateLimiter(value = 2.0, timeout = 300) - @GetMapping("/test3") - public Dict test3() { - log.info("【test3】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } -} diff --git a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java b/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java deleted file mode 100644 index 3317c43..0000000 --- a/spring-boot-demo-ratelimit-guava/src/main/java/com/xkcoding/ratelimit/guava/handler/GlobalExceptionHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.ratelimit.guava.handler; - -import cn.hutool.core.lang.Dict; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -/** - *

    - * 全局异常拦截 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/12 15:00 - */ -@RestControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(RuntimeException.class) - public Dict handler(RuntimeException ex) { - return Dict.create().set("msg", ex.getMessage()); - } -} diff --git a/spring-boot-demo-ratelimit-redis/README.md b/spring-boot-demo-ratelimit-redis/README.md deleted file mode 100644 index e23db4e..0000000 --- a/spring-boot-demo-ratelimit-redis/README.md +++ /dev/null @@ -1,296 +0,0 @@ -# spring-boot-demo-ratelimit-redis - -> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Redis + Lua 脚本实现分布式限流,旨在保护 API 被恶意频繁访问的问题,是 `spring-boot-demo-ratelimit-guava` 的升级版。 - -## 1. 主要代码 - -### 1.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-ratelimit-redis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ratelimit-redis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-ratelimit-redis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 1.2. 限流注解 - -```java -/** - *

    - * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:31 - * @see AnnotationUtils - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface RateLimiter { - long DEFAULT_REQUEST = 10; - - /** - * max 最大请求数 - */ - @AliasFor("max") long value() default DEFAULT_REQUEST; - - /** - * max 最大请求数 - */ - @AliasFor("value") long max() default DEFAULT_REQUEST; - - /** - * 限流key - */ - String key() default ""; - - /** - * 超时时长,默认1分钟 - */ - long timeout() default 1; - - /** - * 超时时间单位,默认 分钟 - */ - TimeUnit timeUnit() default TimeUnit.MINUTES; -} -``` - -### 1.3. AOP处理限流 - -```java -/** - *

    - * 限流切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:30 - */ -@Slf4j -@Aspect -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class RateLimiterAspect { - private final static String SEPARATOR = ":"; - private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; - private final StringRedisTemplate stringRedisTemplate; - private final RedisScript limitRedisScript; - - @Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)") - public void rateLimit() { - - } - - @Around("rateLimit()") - public Object pointcut(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 - RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); - if (rateLimiter != null) { - String key = rateLimiter.key(); - // 默认用类名+方法名做限流的 key 前缀 - if (StrUtil.isBlank(key)) { - key = method.getDeclaringClass().getName()+StrUtil.DOT+method.getName(); - } - // 最终限流的 key 为 前缀 + IP地址 - // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 - key = key + SEPARATOR + IpUtil.getIpAddr(); - - long max = rateLimiter.max(); - long timeout = rateLimiter.timeout(); - TimeUnit timeUnit = rateLimiter.timeUnit(); - boolean limited = shouldLimited(key, max, timeout, timeUnit); - if (limited) { - throw new RuntimeException("手速太快了,慢点儿吧~"); - } - } - - return point.proceed(); - } - - private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { - // 最终的 key 格式为: - // limit:自定义key:IP - // limit:类名.方法名:IP - key = REDIS_LIMIT_KEY_PREFIX + key; - // 统一使用单位毫秒 - long ttl = timeUnit.toMillis(timeout); - // 当前时间毫秒数 - long now = Instant.now().toEpochMilli(); - long expired = now - ttl; - // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String - Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); - if (executeTimes != null) { - if (executeTimes == 0) { - log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); - return true; - } else { - log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); - return false; - } - } - return false; - } -} -``` - -### 1.4. lua 脚本 - -```lua --- 下标从 1 开始 -local key = KEYS[1] -local now = tonumber(ARGV[1]) -local ttl = tonumber(ARGV[2]) -local expired = tonumber(ARGV[3]) --- 最大访问量 -local max = tonumber(ARGV[4]) - --- 清除过期的数据 --- 移除指定分数区间内的所有元素,expired 即已经过期的 score --- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expired -redis.call('zremrangebyscore', key, 0, expired) - --- 获取 zset 中的当前元素个数 -local current = tonumber(redis.call('zcard', key)) -local next = current + 1 - -if next > max then - -- 达到限流大小 返回 0 - return 0; -else - -- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score] - redis.call("zadd", key, now, now) - -- 每次访问均重新设置 zset 的过期时间,单位毫秒 - redis.call("pexpire", key, ttl) - return next -end -``` - -### 1.5. 接口测试 - -```java -/** - *

    - * 测试 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:30 - */ -@Slf4j -@RestController -public class TestController { - - @RateLimiter(value = 5) - @GetMapping("/test1") - public Dict test1() { - log.info("【test1】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } - - @GetMapping("/test2") - public Dict test2() { - log.info("【test2】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); - } - - @RateLimiter(value = 2, key = "测试自定义key") - @GetMapping("/test3") - public Dict test3() { - log.info("【test3】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } -} -``` - -### 1.6. 其余代码参见 demo - -## 2. 测试 - -- 触发限流时控制台打印 - -![image-20190930155856711](http://static.xkcoding.com/spring-boot-demo/ratelimit/redis/063812.jpg) - -- 触发限流的时候 Redis 的数据 - -![image-20190930155735300](http://static.xkcoding.com/spring-boot-demo/ratelimit/redis/063813.jpg) - -## 3. 参考 - -- [mica-plus-redis 的分布式限流实现](https://github.com/lets-mica/mica/tree/master/mica-plus-redis) -- [Java并发:分布式应用限流 Redis + Lua 实践](https://segmentfault.com/a/1190000016042927) diff --git a/spring-boot-demo-ratelimit-redis/pom.xml b/spring-boot-demo-ratelimit-redis/pom.xml deleted file mode 100644 index daf1a85..0000000 --- a/spring-boot-demo-ratelimit-redis/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-ratelimit-redis - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ratelimit-redis - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-aop - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-ratelimit-redis - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java deleted file mode 100644 index 76330b8..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/SpringBootDemoRatelimitRedisApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.ratelimit.redis; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 09:32 - */ -@SpringBootApplication -public class SpringBootDemoRatelimitRedisApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoRatelimitRedisApplication.class, args); - } - -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java deleted file mode 100644 index dda0954..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/annotation/RateLimiter.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.ratelimit.redis.annotation; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.AnnotationUtils; - -import java.lang.annotation.*; -import java.util.concurrent.TimeUnit; - -/** - *

    - * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:31 - * @see AnnotationUtils - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface RateLimiter { - long DEFAULT_REQUEST = 10; - - /** - * max 最大请求数 - */ - @AliasFor("max") long value() default DEFAULT_REQUEST; - - /** - * max 最大请求数 - */ - @AliasFor("value") long max() default DEFAULT_REQUEST; - - /** - * 限流key - */ - String key() default ""; - - /** - * 超时时长,默认1分钟 - */ - long timeout() default 1; - - /** - * 超时时间单位,默认 分钟 - */ - TimeUnit timeUnit() default TimeUnit.MINUTES; -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java deleted file mode 100644 index 4566dfb..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/aspect/RateLimiterAspect.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.xkcoding.ratelimit.redis.aspect; - -import cn.hutool.core.util.StrUtil; -import com.xkcoding.ratelimit.redis.annotation.RateLimiter; -import com.xkcoding.ratelimit.redis.util.IpUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Method; -import java.time.Instant; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -/** - *

    - * 限流切面 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:30 - */ -@Slf4j -@Aspect -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class RateLimiterAspect { - private final static String SEPARATOR = ":"; - private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; - private final StringRedisTemplate stringRedisTemplate; - private final RedisScript limitRedisScript; - - @Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)") - public void rateLimit() { - - } - - @Around("rateLimit()") - public Object pointcut(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 - RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); - if (rateLimiter != null) { - String key = rateLimiter.key(); - // 默认用类名+方法名做限流的 key 前缀 - if (StrUtil.isBlank(key)) { - key = method.getDeclaringClass().getName()+StrUtil.DOT+method.getName(); - } - // 最终限流的 key 为 前缀 + IP地址 - // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 - key = key + SEPARATOR + IpUtil.getIpAddr(); - - long max = rateLimiter.max(); - long timeout = rateLimiter.timeout(); - TimeUnit timeUnit = rateLimiter.timeUnit(); - boolean limited = shouldLimited(key, max, timeout, timeUnit); - if (limited) { - throw new RuntimeException("手速太快了,慢点儿吧~"); - } - } - - return point.proceed(); - } - - private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { - // 最终的 key 格式为: - // limit:自定义key:IP - // limit:类名.方法名:IP - key = REDIS_LIMIT_KEY_PREFIX + key; - // 统一使用单位毫秒 - long ttl = timeUnit.toMillis(timeout); - // 当前时间毫秒数 - long now = Instant.now().toEpochMilli(); - long expired = now - ttl; - // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String - Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); - if (executeTimes != null) { - if (executeTimes == 0) { - log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); - return true; - } else { - log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); - return false; - } - } - return false; - } -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java deleted file mode 100644 index d155b58..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/config/RedisConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xkcoding.ratelimit.redis.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.redis.core.script.DefaultRedisScript; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.scripting.support.ResourceScriptSource; - -/** - *

    - * Redis 配置 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 11:37 - */ -@Configuration -public class RedisConfig { - @Bean - @SuppressWarnings("unchecked") - public RedisScript limitRedisScript() { - DefaultRedisScript redisScript = new DefaultRedisScript<>(); - redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/redis/limit.lua"))); - redisScript.setResultType(Long.class); - return redisScript; - } -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java deleted file mode 100644 index da4f92e..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/controller/TestController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.ratelimit.redis.controller; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.ratelimit.redis.annotation.RateLimiter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 测试 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:30 - */ -@Slf4j -@RestController -public class TestController { - - @RateLimiter(value = 5) - @GetMapping("/test1") - public Dict test1() { - log.info("【test1】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } - - @GetMapping("/test2") - public Dict test2() { - log.info("【test2】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); - } - - @RateLimiter(value = 2, key = "测试自定义key") - @GetMapping("/test3") - public Dict test3() { - log.info("【test3】被执行了。。。。。"); - return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); - } -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java deleted file mode 100644 index 30bdb31..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/handler/GlobalExceptionHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.ratelimit.redis.handler; - -import cn.hutool.core.lang.Dict; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -/** - *

    - * 全局异常拦截 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:30 - */ -@Slf4j -@RestControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(RuntimeException.class) - public Dict handler(RuntimeException ex) { - return Dict.create().set("msg", ex.getMessage()); - } -} diff --git a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java b/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java deleted file mode 100644 index ff2fd39..0000000 --- a/spring-boot-demo-ratelimit-redis/src/main/java/com/xkcoding/ratelimit/redis/util/IpUtil.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.xkcoding.ratelimit.redis.util; - -import cn.hutool.core.util.StrUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * IP 工具类 - *

    - * - * @author yangkai.shen - * @date Created in 2019/9/30 10:38 - */ -@Slf4j -public class IpUtil { - private final static String UNKNOWN = "unknown"; - private final static int MAX_LENGTH = 15; - - /** - * 获取IP地址 - * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 - * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 - */ - public static String getIpAddr() { - HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - String ip = null; - try { - ip = request.getHeader("x-forwarded-for"); - if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (StrUtil.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - } - if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - } - if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - } catch (Exception e) { - log.error("IPUtils ERROR ", e); - } - // 使用代理,则获取第一个IP地址 - if (!StrUtil.isEmpty(ip) && ip.length() > MAX_LENGTH) { - if (ip.indexOf(StrUtil.COMMA) > 0) { - ip = ip.substring(0, ip.indexOf(StrUtil.COMMA)); - } - } - return ip; - } -} diff --git a/spring-boot-demo-rbac-security/README.md b/spring-boot-demo-rbac-security/README.md deleted file mode 100644 index bd7907e..0000000 --- a/spring-boot-demo-rbac-security/README.md +++ /dev/null @@ -1,906 +0,0 @@ -# spring-boot-demo-rbac-security - -> 此 demo 主要演示了 Spring Boot 项目如何集成 Spring Security 完成权限拦截操作。本 demo 为基于**前后端分离**的后端权限管理部分,不同于其他博客里使用的模板技术,希望对大家有所帮助。 - -## 1. 主要功能 - -- [x] 基于 `RBAC` 权限模型设计,详情参考数据库表结构设计 [`security.sql`](./sql/security.sql) -- [x] 支持**动态权限管理**,详情参考 [`RbacAuthorityService.java`](./src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java) -- [x] **登录 / 登出**部分均使用自定义 Controller 实现,未使用 `Spring Security` 内部默认的实现,适用于前后端分离项目,详情参考 [`SecurityConfig.java`](./src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java) 和 [`AuthController.java`](./src/main/java/com/xkcoding/rbac/security/controller/AuthController.java) -- [x] 持久化技术使用 `spring-data-jpa` 完成 -- [x] 使用 `JWT` 实现安全验证,同时引入 `Redis` 解决 `JWT` 无法手动设置过期的弊端,并且保证同一用户在同一时间仅支持同一设备登录,不同设备登录会将,详情参考 [`JwtUtil.java`](./src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java) -- [x] 在线人数统计,详情参考 [`MonitorService.java`](./src/main/java/com/xkcoding/rbac/security/service/MonitorService.java) 和 [`RedisUtil.java`](./src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java) -- [x] 手动踢出用户,详情参考 [`MonitorService.java`](./src/main/java/com/xkcoding/rbac/security/service/MonitorService.java) -- [x] 自定义配置不需要进行拦截的请求,详情参考 [`CustomConfig.java`](./src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java) 和 [`application.yml`](./src/main/resources/application.yml) - -## 2. 运行 - -### 2.1. 环境 - -1. JDK 1.8 以上 -2. Maven 3.5 以上 -3. Mysql 5.7 以上 -4. Redis - -### 2.2. 运行方式 - -1. 新建一个名为 `spring-boot-demo` 的数据库,字符集设置为 `utf-8`,如果数据库名不是 `spring-boot-demo` 需要在 `application.yml` 中修改 `spring.datasource.url` -2. 使用 [`security.sql`](./sql/security.sql) 这个 SQL 文件,创建数据库表和初始化RBAC数据 -3. 运行 `SpringBootDemoRbacSecurityApplication` -4. 管理员账号:admin/123456 普通用户:user/123456 -5. 使用 `POST` 请求访问 `/${contextPath}/api/auth/login` 端点,输入账号密码,登陆成功之后返回token,将获得的 token 放在具体请求的 Header 里,key 固定是 `Authorization` ,value 前缀为 `Bearer 后面加空格`再加token,并加上具体请求的参数,就可以了 -6. enjoy ~​ :kissing_smiling_eyes: - -## 3. 部分关键代码 - -### 3.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-rbac-security - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-rbac-security - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 0.9.1 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - io.jsonwebtoken - jjwt - ${jjwt.veersion} - - - - mysql - mysql-connector-java - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-rbac-security - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 3.2. JwtUtil.java - -> JWT 工具类,主要功能:生成JWT并存入Redis、解析JWT并校验其准确性、从Request的Header中获取JWT - -```java -/** - *

    - * JWT 工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: JWT 工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-07 13:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableConfigurationProperties(JwtConfig.class) -@Configuration -@Slf4j -public class JwtUtil { - @Autowired - private JwtConfig jwtConfig; - - @Autowired - private StringRedisTemplate stringRedisTemplate; - - /** - * 创建JWT - * - * @param rememberMe 记住我 - * @param id 用户id - * @param subject 用户名 - * @param roles 用户角色 - * @param authorities 用户权限 - * @return JWT - */ - public String createJWT(Boolean rememberMe, Long id, String subject, List roles, Collection authorities) { - Date now = new Date(); - JwtBuilder builder = Jwts.builder() - .setId(id.toString()) - .setSubject(subject) - .setIssuedAt(now) - .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) - .claim("roles", roles) - .claim("authorities", authorities); - - // 设置过期时间 - Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); - if (ttl > 0) { - builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue())); - } - - String jwt = builder.compact(); - // 将生成的JWT保存至Redis - stringRedisTemplate.opsForValue() - .set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS); - return jwt; - } - - /** - * 创建JWT - * - * @param authentication 用户认证信息 - * @param rememberMe 记住我 - * @return JWT - */ - public String createJWT(Authentication authentication, Boolean rememberMe) { - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); - } - - /** - * 解析JWT - * - * @param jwt JWT - * @return {@link Claims} - */ - public Claims parseJWT(String jwt) { - try { - Claims claims = Jwts.parser() - .setSigningKey(jwtConfig.getKey()) - .parseClaimsJws(jwt) - .getBody(); - - String username = claims.getSubject(); - String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username; - - // 校验redis中的JWT是否存在 - Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); - if (Objects.isNull(expire) || expire <= 0) { - throw new SecurityException(Status.TOKEN_EXPIRED); - } - - // 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期 - String redisToken = stringRedisTemplate.opsForValue() - .get(redisKey); - if (!StrUtil.equals(jwt, redisToken)) { - throw new SecurityException(Status.TOKEN_OUT_OF_CTRL); - } - return claims; - } catch (ExpiredJwtException e) { - log.error("Token 已过期"); - throw new SecurityException(Status.TOKEN_EXPIRED); - } catch (UnsupportedJwtException e) { - log.error("不支持的 Token"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (MalformedJwtException e) { - log.error("Token 无效"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (SignatureException e) { - log.error("无效的 Token 签名"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (IllegalArgumentException e) { - log.error("Token 参数不存在"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } - } - - /** - * 设置JWT过期 - * - * @param request 请求 - */ - public void invalidateJWT(HttpServletRequest request) { - String jwt = getJwtFromRequest(request); - String username = getUsernameFromJWT(jwt); - // 从redis中清除JWT - stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username); - } - - /** - * 根据 jwt 获取用户名 - * - * @param jwt JWT - * @return 用户名 - */ - public String getUsernameFromJWT(String jwt) { - Claims claims = parseJWT(jwt); - return claims.getSubject(); - } - - /** - * 从 request 的 header 中获取 JWT - * - * @param request 请求 - * @return JWT - */ - public String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - -} -``` - -### 3.3. SecurityConfig.java - -> Spring Security 配置类,主要功能:配置哪些URL不需要认证,哪些需要认证 - -```java -/** - *

    - * Security 配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: Security 配置 - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:46 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableWebSecurity -@EnableConfigurationProperties(CustomConfig.class) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Autowired - private CustomConfig customConfig; - - @Autowired - private AccessDeniedHandler accessDeniedHandler; - - @Autowired - private CustomUserDetailsService customUserDetailsService; - - @Autowired - private JwtAuthenticationFilter jwtAuthenticationFilter; - - @Bean - public BCryptPasswordEncoder encoder() { - return new BCryptPasswordEncoder(); - } - - @Override - @Bean - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(customUserDetailsService) - .passwordEncoder(encoder()); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.cors() - - // 关闭 CSRF - .and() - .csrf() - .disable() - - // 登录行为由自己实现,参考 AuthController#login - .formLogin() - .disable() - .httpBasic() - .disable() - - // 认证请求 - .authorizeRequests() - // 所有请求都需要登录访问 - .anyRequest() - .authenticated() - // RBAC 动态 url 认证 - .anyRequest() - .access("@rbacAuthorityService.hasPermission(request,authentication)") - - // 登出行为由自己实现,参考 AuthController#logout - .and() - .logout() - .disable() - - // Session 管理 - .sessionManagement() - // 因为使用了JWT,所以这里不管理Session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - - // 异常处理 - .and() - .exceptionHandling() - .accessDeniedHandler(accessDeniedHandler); - - // 添加自定义 JWT 过滤器 - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - } - - /** - * 放行所有不需要登录就可以访问的请求,参见 AuthController - * 也可以在 {@link #configure(HttpSecurity)} 中配置 - * {@code http.authorizeRequests().antMatchers("/api/auth/**").permitAll()} - */ - @Override - public void configure(WebSecurity web) { - WebSecurity and = web.ignoring() - .and(); - - // 忽略 GET - customConfig.getIgnores() - .getGet() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.GET, url)); - - // 忽略 POST - customConfig.getIgnores() - .getPost() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.POST, url)); - - // 忽略 DELETE - customConfig.getIgnores() - .getDelete() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.DELETE, url)); - - // 忽略 PUT - customConfig.getIgnores() - .getPut() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.PUT, url)); - - // 忽略 HEAD - customConfig.getIgnores() - .getHead() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.HEAD, url)); - - // 忽略 PATCH - customConfig.getIgnores() - .getPatch() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.PATCH, url)); - - // 忽略 OPTIONS - customConfig.getIgnores() - .getOptions() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.OPTIONS, url)); - - // 忽略 TRACE - customConfig.getIgnores() - .getTrace() - .forEach(url -> and.ignoring() - .antMatchers(HttpMethod.TRACE, url)); - - // 按照请求格式忽略 - customConfig.getIgnores() - .getPattern() - .forEach(url -> and.ignoring() - .antMatchers(url)); - - } -} -``` - -### 3.4. RbacAuthorityService.java - -> 路由动态鉴权类,主要功能: -> -> 1. 校验请求的合法性,排除404和405这两种异常请求 -> 2. 根据当前请求路径与该用户可访问的资源做匹配,通过则可以访问,否则,不允许访问 - -```java -/** - *

    - * 动态路由认证 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: 动态路由认证 - * @author: yangkai.shen - * @date: Created in 2018-12-10 17:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public class RbacAuthorityService { - @Autowired - private RoleDao roleDao; - - @Autowired - private PermissionDao permissionDao; - - @Autowired - private RequestMappingHandlerMapping mapping; - - public boolean hasPermission(HttpServletRequest request, Authentication authentication) { - checkRequest(request); - - Object userInfo = authentication.getPrincipal(); - boolean hasPermission = false; - - if (userInfo instanceof UserDetails) { - UserPrincipal principal = (UserPrincipal) userInfo; - Long userId = principal.getId(); - - List roles = roleDao.selectByUserId(userId); - List roleIds = roles.stream() - .map(Role::getId) - .collect(Collectors.toList()); - List permissions = permissionDao.selectByRoleIdList(roleIds); - - //获取资源,前后端分离,所以过滤页面权限,只保留按钮权限 - List btnPerms = permissions.stream() - // 过滤页面权限 - .filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON)) - // 过滤 URL 为空 - .filter(permission -> StrUtil.isNotBlank(permission.getUrl())) - // 过滤 METHOD 为空 - .filter(permission -> StrUtil.isNotBlank(permission.getMethod())) - .collect(Collectors.toList()); - - for (Permission btnPerm : btnPerms) { - AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod()); - if (antPathMatcher.matches(request)) { - hasPermission = true; - break; - } - } - - return hasPermission; - } else { - return false; - } - } - - /** - * 校验请求是否存在 - * - * @param request 请求 - */ - private void checkRequest(HttpServletRequest request) { - // 获取当前 request 的方法 - String currentMethod = request.getMethod(); - Multimap urlMapping = allUrlMapping(); - - for (String uri : urlMapping.keySet()) { - // 通过 AntPathRequestMatcher 匹配 url - // 可以通过 2 种方式创建 AntPathRequestMatcher - // 1:new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配,因为这里我们把 方法不匹配 自定义抛出,所以,我们使用第2种方式创建 - // 2:new AntPathRequestMatcher(uri) 这种方式不校验请求方法,只校验请求路径 - AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri); - if (antPathMatcher.matches(request)) { - if (!urlMapping.get(uri) - .contains(currentMethod)) { - throw new SecurityException(Status.HTTP_BAD_METHOD); - } else { - return; - } - } - } - - throw new SecurityException(Status.REQUEST_NOT_FOUND); - } - - /** - * 获取 所有URL Mapping,返回格式为{"/test":["GET","POST"],"/sys":["GET","DELETE"]} - * - * @return {@link ArrayListMultimap} 格式的 URL Mapping - */ - private Multimap allUrlMapping() { - Multimap urlMapping = ArrayListMultimap.create(); - - // 获取url与类和方法的对应信息 - Map handlerMethods = mapping.getHandlerMethods(); - - handlerMethods.forEach((k, v) -> { - // 获取当前 key 下的获取所有URL - Set url = k.getPatternsCondition() - .getPatterns(); - RequestMethodsRequestCondition method = k.getMethodsCondition(); - - // 为每个URL添加所有的请求方法 - url.forEach(s -> urlMapping.putAll(s, method.getMethods() - .stream() - .map(Enum::toString) - .collect(Collectors.toList()))); - }); - - return urlMapping; - } -} -``` - -### 3.5. JwtAuthenticationFilter.java - -> JWT 认证过滤器,主要功能: -> -> 1. 过滤不需要拦截的请求 -> 2. 根据当前请求的JWT,认证用户身份信息 - -```java -/** - *

    - * Jwt 认证过滤器 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: Jwt 认证过滤器 - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class JwtAuthenticationFilter extends OncePerRequestFilter { - @Autowired - private CustomUserDetailsService customUserDetailsService; - - @Autowired - private JwtUtil jwtUtil; - - @Autowired - private CustomConfig customConfig; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - - if (checkIgnores(request)) { - filterChain.doFilter(request, response); - return; - } - - String jwt = jwtUtil.getJwtFromRequest(request); - - if (StrUtil.isNotBlank(jwt)) { - try { - String username = jwtUtil.getUsernameFromJWT(jwt); - - UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext() - .setAuthentication(authentication); - filterChain.doFilter(request, response); - } catch (SecurityException e) { - ResponseUtil.renderJson(response, e); - } - } else { - ResponseUtil.renderJson(response, Status.UNAUTHORIZED, null); - } - - } - - /** - * 请求是否不需要进行权限拦截 - * - * @param request 当前请求 - * @return true - 忽略,false - 不忽略 - */ - private boolean checkIgnores(HttpServletRequest request) { - String method = request.getMethod(); - - HttpMethod httpMethod = HttpMethod.resolve(method); - if (ObjectUtil.isNull(httpMethod)) { - httpMethod = HttpMethod.GET; - } - - Set ignores = Sets.newHashSet(); - - switch (httpMethod) { - case GET: - ignores.addAll(customConfig.getIgnores() - .getGet()); - break; - case PUT: - ignores.addAll(customConfig.getIgnores() - .getPut()); - break; - case HEAD: - ignores.addAll(customConfig.getIgnores() - .getHead()); - break; - case POST: - ignores.addAll(customConfig.getIgnores() - .getPost()); - break; - case PATCH: - ignores.addAll(customConfig.getIgnores() - .getPatch()); - break; - case TRACE: - ignores.addAll(customConfig.getIgnores() - .getTrace()); - break; - case DELETE: - ignores.addAll(customConfig.getIgnores() - .getDelete()); - break; - case OPTIONS: - ignores.addAll(customConfig.getIgnores() - .getOptions()); - break; - default: - break; - } - - ignores.addAll(customConfig.getIgnores() - .getPattern()); - - if (CollUtil.isNotEmpty(ignores)) { - for (String ignore : ignores) { - AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method); - if (matcher.matches(request)) { - return true; - } - } - } - - return false; - } - -} -``` - -### 3.6. CustomUserDetailsService.java - -> 实现 `UserDetailsService` 接口,主要功能:根据用户名查询用户信息 - -```java -/** - *

    - * 自定义UserDetails查询 - *

    - * - * @package: com.xkcoding.rbac.security.service - * @description: 自定义UserDetails查询 - * @author: yangkai.shen - * @date: Created in 2018-12-10 10:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class CustomUserDetailsService implements UserDetailsService { - @Autowired - private UserDao userDao; - - @Autowired - private RoleDao roleDao; - - @Autowired - private PermissionDao permissionDao; - - @Override - public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException { - User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone) - .orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone)); - List roles = roleDao.selectByUserId(user.getId()); - List roleIds = roles.stream() - .map(Role::getId) - .collect(Collectors.toList()); - List permissions = permissionDao.selectByRoleIdList(roleIds); - return UserPrincipal.create(user, roles, permissions); - } -} -``` - -### 3.7. RedisUtil.java - -> 主要功能:根据key的格式分页获取Redis存在的key列表 - -```java -/** - *

    - * Redis工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: Redis工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-11 20:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class RedisUtil { - @Autowired - private StringRedisTemplate stringRedisTemplate; - - /** - * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率 - * - * @param patternKey key格式 - * @param currentPage 当前页码 - * @param pageSize 每页条数 - * @return 分页获取指定格式key - */ - public PageResult findKeysForPage(String patternKey, int currentPage, int pageSize) { - ScanOptions options = ScanOptions.scanOptions() - .match(patternKey) - .build(); - RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); - RedisConnection rc = factory.getConnection(); - Cursor cursor = rc.scan(options); - - List result = Lists.newArrayList(); - - long tmpIndex = 0; - int startIndex = (currentPage - 1) * pageSize; - int end = currentPage * pageSize; - while (cursor.hasNext()) { - String key = new String(cursor.next()); - if (tmpIndex >= startIndex && tmpIndex < end) { - result.add(key); - } - tmpIndex++; - } - - try { - cursor.close(); - RedisConnectionUtils.releaseConnection(rc, factory); - } catch (Exception e) { - log.warn("Redis连接关闭异常,", e); - } - - return new PageResult<>(result, tmpIndex); - } -} -``` - -### 3.8. MonitorService.java - -> 监控服务,主要功能:查询当前在线人数分页列表,手动踢出某个用户 - -```java -package com.xkcoding.rbac.security.service; - -import cn.hutool.core.util.StrUtil; -import com.google.common.collect.Lists; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.common.PageResult; -import com.xkcoding.rbac.security.model.User; -import com.xkcoding.rbac.security.repository.UserDao; -import com.xkcoding.rbac.security.util.RedisUtil; -import com.xkcoding.rbac.security.vo.OnlineUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * 监控 Service - *

    - * - * @package: com.xkcoding.rbac.security.service - * @description: 监控 Service - * @author: yangkai.shen - * @date: Created in 2018-12-12 00:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class MonitorService { - @Autowired - private RedisUtil redisUtil; - - @Autowired - private UserDao userDao; - - public PageResult onlineUser(Integer page, Integer size) { - PageResult keys = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, page, size); - List rows = keys.getRows(); - Long total = keys.getTotal(); - - // 根据 redis 中键获取用户名列表 - List usernameList = rows.stream() - .map(s -> StrUtil.subAfter(s, Consts.REDIS_JWT_KEY_PREFIX, true)) - .collect(Collectors.toList()); - // 根据用户名查询用户信息 - List userList = userDao.findByUsernameIn(usernameList); - - // 封装在线用户信息 - List onlineUserList = Lists.newArrayList(); - userList.forEach(user -> onlineUserList.add(OnlineUser.create(user))); - - return new PageResult<>(onlineUserList, total); - } -} -``` - -### 3.9. 其余代码参见本 demo - -## 4. 参考 - -1. Spring Security 官方文档:https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/ -2. JWT 官网:https://jwt.io/ -3. JJWT开源工具参考:https://github.com/jwtk/jjwt#quickstart -4. 授权部分参考官方文档:https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/#authorization - -4. 动态授权部分,参考博客:https://blog.csdn.net/larger5/article/details/81063438 - diff --git a/spring-boot-demo-rbac-security/pom.xml b/spring-boot-demo-rbac-security/pom.xml deleted file mode 100644 index 56c1f99..0000000 --- a/spring-boot-demo-rbac-security/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-rbac-security - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-rbac-security - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 0.9.1 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - io.jsonwebtoken - jjwt - ${jjwt.veersion} - - - - mysql - mysql-connector-java - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-rbac-security - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java deleted file mode 100644 index 5b8cef0..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/SpringBootDemoRbacSecurityApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.rbac.security; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.rbac.security - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoRbacSecurityApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoRbacSecurityApplication.class, args); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java deleted file mode 100644 index 8d743c3..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/ApiResponse.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.xkcoding.rbac.security.common; - -import lombok.Data; - -import java.io.Serializable; - -/** - *

    - * 通用的 API 接口封装 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: 通用的 API 接口封装 - * @author: yangkai.shen - * @date: Created in 2018-12-07 14:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ApiResponse implements Serializable { - private static final long serialVersionUID = 8993485788201922830L; - - /** - * 状态码 - */ - private Integer code; - - /** - * 返回内容 - */ - private String message; - - /** - * 返回数据 - */ - private Object data; - - /** - * 无参构造函数 - */ - private ApiResponse() { - - } - - /** - * 全参构造函数 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - */ - private ApiResponse(Integer code, String message, Object data) { - this.code = code; - this.message = message; - this.data = data; - } - - /** - * 构造一个自定义的API返回 - * - * @param code 状态码 - * @param message 返回内容 - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse of(Integer code, String message, Object data) { - return new ApiResponse(code, message, data); - } - - /** - * 构造一个成功且不带数据的API返回 - * - * @return ApiResponse - */ - public static ApiResponse ofSuccess() { - return ofSuccess(null); - } - - /** - * 构造一个成功且带数据的API返回 - * - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofSuccess(Object data) { - return ofStatus(Status.SUCCESS, data); - } - - /** - * 构造一个成功且自定义消息的API返回 - * - * @param message 返回内容 - * @return ApiResponse - */ - public static ApiResponse ofMessage(String message) { - return of(Status.SUCCESS.getCode(), message, null); - } - - /** - * 构造一个有状态的API返回 - * - * @param status 状态 {@link Status} - * @return ApiResponse - */ - public static ApiResponse ofStatus(Status status) { - return ofStatus(status, null); - } - - /** - * 构造一个有状态且带数据的API返回 - * - * @param status 状态 {@link IStatus} - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ofStatus(IStatus status, Object data) { - return of(status.getCode(), status.getMessage(), data); - } - - /** - * 构造一个异常的API返回 - * - * @param t 异常 - * @param {@link BaseException} 的子类 - * @return ApiResponse - */ - public static ApiResponse ofException(T t) { - return of(t.getCode(), t.getMessage(), t.getData()); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java deleted file mode 100644 index 90547ef..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/BaseException.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.rbac.security.common; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

    - * 异常基类 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: 异常基类 - * @author: yangkai.shen - * @date: Created in 2018-12-07 14:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EqualsAndHashCode(callSuper = true) -@Data -public class BaseException extends RuntimeException { - private Integer code; - private String message; - private Object data; - - public BaseException(Status status) { - super(status.getMessage()); - this.code = status.getCode(); - this.message = status.getMessage(); - } - - public BaseException(Status status, Object data) { - this(status); - this.data = data; - } - - public BaseException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } - - public BaseException(Integer code, String message, Object data) { - this(code, message); - this.data = data; - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java deleted file mode 100644 index 8e97373..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.xkcoding.rbac.security.common; - -/** - *

    - * 常量池 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: 常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface Consts { - /** - * 启用 - */ - Integer ENABLE = 1; - /** - * 禁用 - */ - Integer DISABLE = 0; - - /** - * 页面 - */ - Integer PAGE = 1; - - /** - * 按钮 - */ - Integer BUTTON = 2; - - /** - * JWT 在 Redis 中保存的key前缀 - */ - String REDIS_JWT_KEY_PREFIX = "security:jwt:"; - - /** - * 星号 - */ - String SYMBOL_STAR = "*"; - - /** - * 邮箱符号 - */ - String SYMBOL_EMAIL = "@"; - - /** - * 默认当前页码 - */ - Integer DEFAULT_CURRENT_PAGE = 1; - - /** - * 默认每页条数 - */ - Integer DEFAULT_PAGE_SIZE = 10; - - /** - * 匿名用户 用户名 - */ - String ANONYMOUS_NAME = "匿名用户"; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java deleted file mode 100644 index eefd3a2..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/IStatus.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.rbac.security.common; - -/** - *

    - * REST API 错误码接口 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: REST API 错误码接口 - * @author: yangkai.shen - * @date: Created in 2018-12-07 14:35 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface IStatus { - - /** - * 状态码 - * - * @return 状态码 - */ - Integer getCode(); - - /** - * 返回信息 - * - * @return 返回信息 - */ - String getMessage(); - -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java deleted file mode 100644 index 4a2307e..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.xkcoding.rbac.security.common; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.List; - -/** - *

    - * 通用分页参数返回 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: 通用分页参数返回 - * @author: yangkai.shen - * @date: Created in 2018-12-11 20:26 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class PageResult implements Serializable { - private static final long serialVersionUID = 3420391142991247367L; - - /** - * 当前页数据 - */ - private List rows; - - /** - * 总条数 - */ - private Long total; - - public static PageResult of(List rows, Long total) { - return new PageResult<>(rows, total); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java deleted file mode 100644 index 23a194e..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Status.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.xkcoding.rbac.security.common; - -import lombok.Getter; - -/** - *

    - * 通用状态码 - *

    - * - * @package: com.xkcoding.rbac.security.common - * @description: 通用状态码 - * @author: yangkai.shen - * @date: Created in 2018-12-07 14:31 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public enum Status implements IStatus { - /** - * 操作成功! - */ - SUCCESS(200, "操作成功!"), - - /** - * 操作异常! - */ - ERROR(500, "操作异常!"), - - /** - * 退出成功! - */ - LOGOUT(200, "退出成功!"), - - /** - * 请先登录! - */ - UNAUTHORIZED(401, "请先登录!"), - - /** - * 暂无权限访问! - */ - ACCESS_DENIED(403, "权限不足!"), - - /** - * 请求不存在! - */ - REQUEST_NOT_FOUND(404, "请求不存在!"), - - /** - * 请求方式不支持! - */ - HTTP_BAD_METHOD(405, "请求方式不支持!"), - - /** - * 请求异常! - */ - BAD_REQUEST(400, "请求异常!"), - - /** - * 参数不匹配! - */ - PARAM_NOT_MATCH(400, "参数不匹配!"), - - /** - * 参数不能为空! - */ - PARAM_NOT_NULL(400, "参数不能为空!"), - - /** - * 当前用户已被锁定,请联系管理员解锁! - */ - USER_DISABLED(403, "当前用户已被锁定,请联系管理员解锁!"), - - /** - * 用户名或密码错误! - */ - USERNAME_PASSWORD_ERROR(5001, "用户名或密码错误!"), - - /** - * token 已过期,请重新登录! - */ - TOKEN_EXPIRED(5002, "token 已过期,请重新登录!"), - - /** - * token 解析失败,请尝试重新登录! - */ - TOKEN_PARSE_ERROR(5002, "token 解析失败,请尝试重新登录!"), - - /** - * 当前用户已在别处登录,请尝试更改密码或重新登录! - */ - TOKEN_OUT_OF_CTRL(5003, "当前用户已在别处登录,请尝试更改密码或重新登录!"), - - /** - * 无法手动踢出自己,请尝试退出登录操作! - */ - KICKOUT_SELF(5004, "无法手动踢出自己,请尝试退出登录操作!"); - - /** - * 状态码 - */ - private Integer code; - - /** - * 返回信息 - */ - private String message; - - Status(Integer code, String message) { - this.code = code; - this.message = message; - } - - public static Status fromCode(Integer code) { - Status[] statuses = Status.values(); - for (Status status : statuses) { - if (status.getCode() - .equals(code)) { - return status; - } - } - return SUCCESS; - } - - @Override - public String toString() { - return String.format(" Status:{code=%s, message=%s} ", getCode(), getMessage()); - } - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java deleted file mode 100644 index 32f3cf7..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/CustomConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - *

    - * 自定义配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: 自定义配置 - * @author: yangkai.shen - * @date: Created in 2018-12-13 10:56 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ConfigurationProperties(prefix = "custom.config") -@Data -public class CustomConfig { - /** - * 不需要拦截的地址 - */ - private IgnoreConfig ignores; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java deleted file mode 100644 index d25738a..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IdConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import cn.hutool.core.lang.Snowflake; -import cn.hutool.core.util.IdUtil; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * 雪花主键生成器 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: 雪花主键生成器 - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class IdConfig { - /** - * 雪花生成器 - */ - @Bean - public Snowflake snowflake() { - return IdUtil.createSnowflake(1, 1); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java deleted file mode 100644 index 2bbc40f..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/IgnoreConfig.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import com.google.common.collect.Lists; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 忽略配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: 忽略配置 - * @author: yangkai.shen - * @date: Created in 2018-12-17 17:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class IgnoreConfig { - /** - * 需要忽略的 URL 格式,不考虑请求方法 - */ - private List pattern = Lists.newArrayList(); - - /** - * 需要忽略的 GET 请求 - */ - private List get = Lists.newArrayList(); - - /** - * 需要忽略的 POST 请求 - */ - private List post = Lists.newArrayList(); - - /** - * 需要忽略的 DELETE 请求 - */ - private List delete = Lists.newArrayList(); - - /** - * 需要忽略的 PUT 请求 - */ - private List put = Lists.newArrayList(); - - /** - * 需要忽略的 HEAD 请求 - */ - private List head = Lists.newArrayList(); - - /** - * 需要忽略的 PATCH 请求 - */ - private List patch = Lists.newArrayList(); - - /** - * 需要忽略的 OPTIONS 请求 - */ - private List options = Lists.newArrayList(); - - /** - * 需要忽略的 TRACE 请求 - */ - private List trace = Lists.newArrayList(); -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java deleted file mode 100644 index 165d230..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtAuthenticationFilter.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.google.common.collect.Sets; -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.exception.SecurityException; -import com.xkcoding.rbac.security.service.CustomUserDetailsService; -import com.xkcoding.rbac.security.util.JwtUtil; -import com.xkcoding.rbac.security.util.ResponseUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Set; - -/** - *

    - * Jwt 认证过滤器 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: Jwt 认证过滤器 - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:15 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class JwtAuthenticationFilter extends OncePerRequestFilter { - @Autowired - private CustomUserDetailsService customUserDetailsService; - - @Autowired - private JwtUtil jwtUtil; - - @Autowired - private CustomConfig customConfig; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - - if (checkIgnores(request)) { - filterChain.doFilter(request, response); - return; - } - - String jwt = jwtUtil.getJwtFromRequest(request); - - if (StrUtil.isNotBlank(jwt)) { - try { - String username = jwtUtil.getUsernameFromJWT(jwt); - - UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext() - .setAuthentication(authentication); - filterChain.doFilter(request, response); - } catch (SecurityException e) { - ResponseUtil.renderJson(response, e); - } - } else { - ResponseUtil.renderJson(response, Status.UNAUTHORIZED, null); - } - - } - - /** - * 请求是否不需要进行权限拦截 - * - * @param request 当前请求 - * @return true - 忽略,false - 不忽略 - */ - private boolean checkIgnores(HttpServletRequest request) { - String method = request.getMethod(); - - HttpMethod httpMethod = HttpMethod.resolve(method); - if (ObjectUtil.isNull(httpMethod)) { - httpMethod = HttpMethod.GET; - } - - Set ignores = Sets.newHashSet(); - - switch (httpMethod) { - case GET: - ignores.addAll(customConfig.getIgnores() - .getGet()); - break; - case PUT: - ignores.addAll(customConfig.getIgnores() - .getPut()); - break; - case HEAD: - ignores.addAll(customConfig.getIgnores() - .getHead()); - break; - case POST: - ignores.addAll(customConfig.getIgnores() - .getPost()); - break; - case PATCH: - ignores.addAll(customConfig.getIgnores() - .getPatch()); - break; - case TRACE: - ignores.addAll(customConfig.getIgnores() - .getTrace()); - break; - case DELETE: - ignores.addAll(customConfig.getIgnores() - .getDelete()); - break; - case OPTIONS: - ignores.addAll(customConfig.getIgnores() - .getOptions()); - break; - default: - break; - } - - ignores.addAll(customConfig.getIgnores() - .getPattern()); - - if (CollUtil.isNotEmpty(ignores)) { - for (String ignore : ignores) { - AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method); - if (matcher.matches(request)) { - return true; - } - } - } - - return false; - } - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java deleted file mode 100644 index 1cc0988..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/JwtConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - *

    - * JWT 配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: JWT 配置 - * @author: yangkai.shen - * @date: Created in 2018-12-07 13:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ConfigurationProperties(prefix = "jwt.config") -@Data -public class JwtConfig { - /** - * jwt 加密 key,默认值:xkcoding. - */ - private String key = "xkcoding"; - - /** - * jwt 过期时间,默认值:600000 {@code 10 分钟}. - */ - private Long ttl = 600000L; - - /** - * 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} - */ - private Long remember = 604800000L; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java deleted file mode 100644 index 1c25012..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import cn.hutool.core.util.StrUtil; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.exception.SecurityException; -import com.xkcoding.rbac.security.model.Permission; -import com.xkcoding.rbac.security.model.Role; -import com.xkcoding.rbac.security.repository.PermissionDao; -import com.xkcoding.rbac.security.repository.RoleDao; -import com.xkcoding.rbac.security.vo.UserPrincipal; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import javax.servlet.http.HttpServletRequest; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -/** - *

    - * 动态路由认证 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: 动态路由认证 - * @author: yangkai.shen - * @date: Created in 2018-12-10 17:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public class RbacAuthorityService { - @Autowired - private RoleDao roleDao; - - @Autowired - private PermissionDao permissionDao; - - @Autowired - private RequestMappingHandlerMapping mapping; - - public boolean hasPermission(HttpServletRequest request, Authentication authentication) { - checkRequest(request); - - Object userInfo = authentication.getPrincipal(); - boolean hasPermission = false; - - if (userInfo instanceof UserDetails) { - UserPrincipal principal = (UserPrincipal) userInfo; - Long userId = principal.getId(); - - List roles = roleDao.selectByUserId(userId); - List roleIds = roles.stream() - .map(Role::getId) - .collect(Collectors.toList()); - List permissions = permissionDao.selectByRoleIdList(roleIds); - - //获取资源,前后端分离,所以过滤页面权限,只保留按钮权限 - List btnPerms = permissions.stream() - // 过滤页面权限 - .filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON)) - // 过滤 URL 为空 - .filter(permission -> StrUtil.isNotBlank(permission.getUrl())) - // 过滤 METHOD 为空 - .filter(permission -> StrUtil.isNotBlank(permission.getMethod())) - .collect(Collectors.toList()); - - for (Permission btnPerm : btnPerms) { - AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod()); - if (antPathMatcher.matches(request)) { - hasPermission = true; - break; - } - } - - return hasPermission; - } else { - return false; - } - } - - /** - * 校验请求是否存在 - * - * @param request 请求 - */ - private void checkRequest(HttpServletRequest request) { - // 获取当前 request 的方法 - String currentMethod = request.getMethod(); - Multimap urlMapping = allUrlMapping(); - - for (String uri : urlMapping.keySet()) { - // 通过 AntPathRequestMatcher 匹配 url - // 可以通过 2 种方式创建 AntPathRequestMatcher - // 1:new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配,因为这里我们把 方法不匹配 自定义抛出,所以,我们使用第2种方式创建 - // 2:new AntPathRequestMatcher(uri) 这种方式不校验请求方法,只校验请求路径 - AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri); - if (antPathMatcher.matches(request)) { - if (!urlMapping.get(uri) - .contains(currentMethod)) { - throw new SecurityException(Status.HTTP_BAD_METHOD); - } else { - return; - } - } - } - - throw new SecurityException(Status.REQUEST_NOT_FOUND); - } - - /** - * 获取 所有URL Mapping,返回格式为{"/test":["GET","POST"],"/sys":["GET","DELETE"]} - * - * @return {@link ArrayListMultimap} 格式的 URL Mapping - */ - private Multimap allUrlMapping() { - Multimap urlMapping = ArrayListMultimap.create(); - - // 获取url与类和方法的对应信息 - Map handlerMethods = mapping.getHandlerMethods(); - - handlerMethods.forEach((k, v) -> { - // 获取当前 key 下的获取所有URL - Set url = k.getPatternsCondition() - .getPatterns(); - RequestMethodsRequestCondition method = k.getMethodsCondition(); - - // 为每个URL添加所有的请求方法 - url.forEach(s -> urlMapping.putAll(s, method.getMethods() - .stream() - .map(Enum::toString) - .collect(Collectors.toList()))); - }); - - return urlMapping; - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java deleted file mode 100644 index 5ab166e..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/RedisConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -import java.io.Serializable; - -/** - *

    - * redis配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: redis配置 - * @author: yangkai.shen - * @date: Created in 2018-12-11 15:16 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@AutoConfigureAfter(RedisAutoConfiguration.class) -@EnableCaching -public class RedisConfig { - - /** - * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 - */ - @Bean - public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); - template.setConnectionFactory(redisConnectionFactory); - return template; - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java deleted file mode 100644 index a9bf36e..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import com.xkcoding.rbac.security.service.CustomUserDetailsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -/** - *

    - * Security 配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: Security 配置 - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:46 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableWebSecurity -@EnableConfigurationProperties(CustomConfig.class) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Autowired - private CustomConfig customConfig; - - @Autowired - private AccessDeniedHandler accessDeniedHandler; - - @Autowired - private CustomUserDetailsService customUserDetailsService; - - @Autowired - private JwtAuthenticationFilter jwtAuthenticationFilter; - - @Bean - public BCryptPasswordEncoder encoder() { - return new BCryptPasswordEncoder(); - } - - @Override - @Bean - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(customUserDetailsService).passwordEncoder(encoder()); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - // @formatter:off - http.cors() - // 关闭 CSRF - .and().csrf().disable() - // 登录行为由自己实现,参考 AuthController#login - .formLogin().disable() - .httpBasic().disable() - - // 认证请求 - .authorizeRequests() - // 所有请求都需要登录访问 - .anyRequest() - .authenticated() - // RBAC 动态 url 认证 - .anyRequest() - .access("@rbacAuthorityService.hasPermission(request,authentication)") - - // 登出行为由自己实现,参考 AuthController#logout - .and().logout().disable() - // Session 管理 - .sessionManagement() - // 因为使用了JWT,所以这里不管理Session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - - // 异常处理 - .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler); - // @formatter:on - - // 添加自定义 JWT 过滤器 - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - } - - /** - * 放行所有不需要登录就可以访问的请求,参见 AuthController - * 也可以在 {@link #configure(HttpSecurity)} 中配置 - * {@code http.authorizeRequests().antMatchers("/api/auth/**").permitAll()} - */ - @Override - public void configure(WebSecurity web) { - WebSecurity and = web.ignoring().and(); - - // 忽略 GET - customConfig.getIgnores().getGet().forEach(url -> and.ignoring().antMatchers(HttpMethod.GET, url)); - - // 忽略 POST - customConfig.getIgnores().getPost().forEach(url -> and.ignoring().antMatchers(HttpMethod.POST, url)); - - // 忽略 DELETE - customConfig.getIgnores().getDelete().forEach(url -> and.ignoring().antMatchers(HttpMethod.DELETE, url)); - - // 忽略 PUT - customConfig.getIgnores().getPut().forEach(url -> and.ignoring().antMatchers(HttpMethod.PUT, url)); - - // 忽略 HEAD - customConfig.getIgnores().getHead().forEach(url -> and.ignoring().antMatchers(HttpMethod.HEAD, url)); - - // 忽略 PATCH - customConfig.getIgnores().getPatch().forEach(url -> and.ignoring().antMatchers(HttpMethod.PATCH, url)); - - // 忽略 OPTIONS - customConfig.getIgnores().getOptions().forEach(url -> and.ignoring().antMatchers(HttpMethod.OPTIONS, url)); - - // 忽略 TRACE - customConfig.getIgnores().getTrace().forEach(url -> and.ignoring().antMatchers(HttpMethod.TRACE, url)); - - // 按照请求格式忽略 - customConfig.getIgnores().getPattern().forEach(url -> and.ignoring().antMatchers(url)); - - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java deleted file mode 100644 index 2db9de8..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/SecurityHandlerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.util.ResponseUtil; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.web.access.AccessDeniedHandler; - -/** - *

    - * Security 结果处理配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: Security 结果处理配置 - * @author: yangkai.shen - * @date: Created in 2018-12-07 17:31 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class SecurityHandlerConfig { - - @Bean - public AccessDeniedHandler accessDeniedHandler() { - return (request, response, accessDeniedException) -> ResponseUtil.renderJson(response, Status.ACCESS_DENIED, null); - } - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java deleted file mode 100644 index cffb3fa..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/config/WebMvcConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.rbac.security.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - *

    - * MVC配置 - *

    - * - * @package: com.xkcoding.rbac.security.config - * @description: MVC配置 - * @author: yangkai.shen - * @date: Created in 2018-12-10 16:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - private static final long MAX_AGE_SECS = 3600; - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("*") - .allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE") - .maxAge(MAX_AGE_SECS); - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java deleted file mode 100644 index 6e5f578..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.xkcoding.rbac.security.controller; - -import com.xkcoding.rbac.security.common.ApiResponse; -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.exception.SecurityException; -import com.xkcoding.rbac.security.payload.LoginRequest; -import com.xkcoding.rbac.security.util.JwtUtil; -import com.xkcoding.rbac.security.vo.JwtResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; - -/** - *

    - * 认证 Controller,包括用户注册,用户登录请求 - *

    - * - * @package: com.xkcoding.rbac.security.controller - * @description: 认证 Controller,包括用户注册,用户登录请求 - * @author: yangkai.shen - * @date: Created in 2018-12-07 17:23 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RestController -@RequestMapping("/api/auth") -public class AuthController { - - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private JwtUtil jwtUtil; - - /** - * 登录 - */ - @PostMapping("/login") - public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) { - Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmailOrPhone(), loginRequest.getPassword())); - - SecurityContextHolder.getContext() - .setAuthentication(authentication); - - String jwt = jwtUtil.createJWT(authentication,loginRequest.getRememberMe()); - return ApiResponse.ofSuccess(new JwtResponse(jwt)); - } - - @PostMapping("/logout") - public ApiResponse logout(HttpServletRequest request) { - try { - // 设置JWT过期 - jwtUtil.invalidateJWT(request); - } catch (SecurityException e) { - throw new SecurityException(Status.UNAUTHORIZED); - } - return ApiResponse.ofStatus(Status.LOGOUT); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java deleted file mode 100644 index c254f77..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.xkcoding.rbac.security.controller; - -import cn.hutool.core.collection.CollUtil; -import com.xkcoding.rbac.security.common.ApiResponse; -import com.xkcoding.rbac.security.common.PageResult; -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.exception.SecurityException; -import com.xkcoding.rbac.security.payload.PageCondition; -import com.xkcoding.rbac.security.service.MonitorService; -import com.xkcoding.rbac.security.util.PageUtil; -import com.xkcoding.rbac.security.util.SecurityUtil; -import com.xkcoding.rbac.security.vo.OnlineUser; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - *

    - * 监控 Controller,在线用户,手动踢出用户等功能 - *

    - * - * @package: com.xkcoding.rbac.security.controller - * @description: 监控 Controller,在线用户,手动踢出用户等功能 - * @author: yangkai.shen - * @date: Created in 2018-12-11 20:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RestController -@RequestMapping("/api/monitor") -public class MonitorController { - @Autowired - private MonitorService monitorService; - - /** - * 在线用户列表 - * - * @param pageCondition 分页参数 - */ - @GetMapping("/online/user") - public ApiResponse onlineUser(PageCondition pageCondition) { - PageUtil.checkPageCondition(pageCondition, PageCondition.class); - PageResult pageResult = monitorService.onlineUser(pageCondition); - return ApiResponse.ofSuccess(pageResult); - } - - /** - * 批量踢出在线用户 - * - * @param names 用户名列表 - */ - @DeleteMapping("/online/user/kickout") - public ApiResponse kickoutOnlineUser(@RequestBody List names) { - if (CollUtil.isEmpty(names)) { - throw new SecurityException(Status.PARAM_NOT_NULL); - } - if (names.contains(SecurityUtil.getCurrentUsername())){ - throw new SecurityException(Status.KICKOUT_SELF); - } - monitorService.kickout(names); - return ApiResponse.ofSuccess(); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java deleted file mode 100644 index da05212..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/TestController.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.rbac.security.controller; - -import com.xkcoding.rbac.security.common.ApiResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -/** - *

    - * 测试Controller - *

    - * - * @package: com.xkcoding.rbac.security.controller - * @description: 测试Controller - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:44 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RestController -@RequestMapping("/test") -public class TestController { - @GetMapping - public ApiResponse list() { - log.info("测试列表查询"); - return ApiResponse.ofMessage("测试列表查询"); - } - - @PostMapping - public ApiResponse add() { - log.info("测试列表添加"); - return ApiResponse.ofMessage("测试列表添加"); - } - - @PutMapping("/{id}") - public ApiResponse update(@PathVariable Long id) { - log.info("测试列表修改"); - return ApiResponse.ofSuccess("测试列表修改"); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java deleted file mode 100644 index 4b02465..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/SecurityException.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.rbac.security.exception; - -import com.xkcoding.rbac.security.common.BaseException; -import com.xkcoding.rbac.security.common.Status; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

    - * 全局异常 - *

    - * - * @package: com.xkcoding.rbac.security.exception - * @description: 全局异常 - * @author: yangkai.shen - * @date: Created in 2018-12-10 17:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EqualsAndHashCode(callSuper = true) -@Data -public class SecurityException extends BaseException { - public SecurityException(Status status) { - super(status); - } - - public SecurityException(Status status, Object data) { - super(status, data); - } - - public SecurityException(Integer code, String message) { - super(code, message); - } - - public SecurityException(Integer code, String message, Object data) { - super(code, message, data); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java deleted file mode 100644 index 909638c..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/exception/handler/GlobalExceptionHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.xkcoding.rbac.security.exception.handler; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.json.JSONUtil; -import com.xkcoding.rbac.security.common.ApiResponse; -import com.xkcoding.rbac.security.common.BaseException; -import com.xkcoding.rbac.security.common.Status; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.servlet.NoHandlerFoundException; - -import javax.validation.ConstraintViolationException; - -/** - *

    - * 全局统一异常处理 - *

    - * - * @package: com.xkcoding.rbac.security.exception.handler - * @description: 全局统一异常处理 - * @author: yangkai.shen - * @date: Created in 2018-12-10 17:00 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ControllerAdvice -@Slf4j -public class GlobalExceptionHandler { - - @ExceptionHandler(value = Exception.class) - @ResponseBody - public ApiResponse handlerException(Exception e) { - if (e instanceof NoHandlerFoundException) { - log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod()); - return ApiResponse.ofStatus(Status.REQUEST_NOT_FOUND); - } else if (e instanceof HttpRequestMethodNotSupportedException) { - log.error("【全局异常拦截】HttpRequestMethodNotSupportedException: 当前请求方式 {}, 支持请求方式 {}", ((HttpRequestMethodNotSupportedException) e).getMethod(), JSONUtil.toJsonStr(((HttpRequestMethodNotSupportedException) e).getSupportedHttpMethods())); - return ApiResponse.ofStatus(Status.HTTP_BAD_METHOD); - } else if (e instanceof MethodArgumentNotValidException) { - log.error("【全局异常拦截】MethodArgumentNotValidException", e); - return ApiResponse.of(Status.BAD_REQUEST.getCode(), ((MethodArgumentNotValidException) e).getBindingResult() - .getAllErrors() - .get(0) - .getDefaultMessage(), null); - } else if (e instanceof ConstraintViolationException) { - log.error("【全局异常拦截】ConstraintViolationException", e); - return ApiResponse.of(Status.BAD_REQUEST.getCode(), CollUtil.getFirst(((ConstraintViolationException) e).getConstraintViolations()) - .getMessage(), null); - } else if (e instanceof MethodArgumentTypeMismatchException) { - log.error("【全局异常拦截】MethodArgumentTypeMismatchException: 参数名 {}, 异常信息 {}", ((MethodArgumentTypeMismatchException) e).getName(), ((MethodArgumentTypeMismatchException) e).getMessage()); - return ApiResponse.ofStatus(Status.PARAM_NOT_MATCH); - } else if (e instanceof HttpMessageNotReadableException) { - log.error("【全局异常拦截】HttpMessageNotReadableException: 错误信息 {}", ((HttpMessageNotReadableException) e).getMessage()); - return ApiResponse.ofStatus(Status.PARAM_NOT_NULL); - } else if (e instanceof BadCredentialsException) { - log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage()); - return ApiResponse.ofStatus(Status.USERNAME_PASSWORD_ERROR); - } else if (e instanceof DisabledException) { - log.error("【全局异常拦截】BadCredentialsException: 错误信息 {}", e.getMessage()); - return ApiResponse.ofStatus(Status.USER_DISABLED); - } else if (e instanceof BaseException) { - log.error("【全局异常拦截】DataManagerException: 状态码 {}, 异常信息 {}", ((BaseException) e).getCode(), e.getMessage()); - return ApiResponse.ofException((BaseException) e); - } - - log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage()); - return ApiResponse.ofStatus(Status.ERROR); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java deleted file mode 100644 index 1c2c5df..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Permission.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.rbac.security.model; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -/** - *

    - * 权限 - *

    - * - * @package: com.xkcoding.rbac.security.model - * @description: 权限 - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:04 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "sec_permission") -public class Permission { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 权限名 - */ - private String name; - - /** - * 类型为页面时,代表前端路由地址,类型为按钮时,代表后端接口地址 - */ - private String url; - - /** - * 权限类型,页面-1,按钮-2 - */ - private Integer type; - - /** - * 权限表达式 - */ - private String permission; - - /** - * 后端接口访问方式 - */ - private String method; - - /** - * 排序 - */ - private Integer sort; - - /** - * 父级id - */ - @Column(name = "parent_id") - private Long parentId; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java deleted file mode 100644 index 4ed5764..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/Role.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.xkcoding.rbac.security.model; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -/** - *

    - * 角色 - *

    - * - * @package: com.xkcoding.rbac.security.model - * @description: 角色 - * @author: yangkai.shen - * @date: Created in 2018-12-07 15:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "sec_role") -public class Role { - /** - * 主键 - */ - @Id - private Long id; - - /** - * 角色名 - */ - private String name; - - /** - * 描述 - */ - private String description; - - /** - * 创建时间 - */ - @Column(name = "create_time") - private Long createTime; - - /** - * 更新时间 - */ - @Column(name = "update_time") - private Long updateTime; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java deleted file mode 100644 index 3705d8f..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/RolePermission.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.rbac.security.model; - -import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; -import lombok.Data; - -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Table; - -/** - *

    - * 角色-权限 - *

    - * - * @package: com.xkcoding.rbac.security.model - * @description: 角色-权限 - * @author: yangkai.shen - * @date: Created in 2018-12-10 13:46 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "sec_role_permission") -public class RolePermission { - /** - * 主键 - */ - @EmbeddedId - private RolePermissionKey id; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java deleted file mode 100644 index f5db78c..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/User.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.xkcoding.rbac.security.model; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -/** - *

    - * 用户 - *

    - * - * @package: com.xkcoding.rbac.security.model - * @description: 用户 - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:00 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "sec_user") -public class User { - - /** - * 主键 - */ - @Id - private Long id; - - /** - * 用户名 - */ - private String username; - - /** - * 密码 - */ - private String password; - - /** - * 昵称 - */ - private String nickname; - - /** - * 手机 - */ - private String phone; - - /** - * 邮箱 - */ - private String email; - - /** - * 生日 - */ - private Long birthday; - - /** - * 性别,男-1,女-2 - */ - private Integer sex; - - /** - * 状态,启用-1,禁用-0 - */ - private Integer status; - - /** - * 创建时间 - */ - @Column(name = "create_time") - private Long createTime; - - /** - * 更新时间 - */ - @Column(name = "update_time") - private Long updateTime; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java deleted file mode 100644 index af38984..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/UserRole.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.rbac.security.model; - -import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; -import lombok.Data; - -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Table; - -/** - *

    - * 用户角色关联 - *

    - * - * @package: com.xkcoding.rbac.security.model - * @description: 用户角色关联 - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Entity -@Table(name = "sec_user_role") -public class UserRole { - /** - * 主键 - */ - @EmbeddedId - private UserRoleKey id; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java deleted file mode 100644 index 8837ca8..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/RolePermissionKey.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.rbac.security.model.unionkey; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Embeddable; -import java.io.Serializable; - -/** - *

    - * 角色-权限联合主键 - *

    - * - * @package: com.xkcoding.rbac.security.model.unionkey - * @description: 角色-权限联合主键 - * @author: yangkai.shen - * @date: Created in 2018-12-10 13:47 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Embeddable -public class RolePermissionKey implements Serializable { - private static final long serialVersionUID = 6850974328279713855L; - - /** - * 角色id - */ - @Column(name = "role_id") - private Long roleId; - - /** - * 权限id - */ - @Column(name = "permission_id") - private Long permissionId; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java deleted file mode 100644 index bc9d548..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/model/unionkey/UserRoleKey.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xkcoding.rbac.security.model.unionkey; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Embeddable; -import java.io.Serializable; - -/** - *

    - * 用户-角色联合主键 - *

    - * - * @package: com.xkcoding.rbac.security.model.unionkey - * @description: 用户-角色联合主键 - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:20 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Embeddable -@Data -public class UserRoleKey implements Serializable { - private static final long serialVersionUID = 5633412144183654743L; - - /** - * 用户id - */ - @Column(name = "user_id") - private Long userId; - - /** - * 角色id - */ - @Column(name = "role_id") - private Long roleId; -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java deleted file mode 100644 index 3c9a0c5..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/LoginRequest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.rbac.security.payload; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; - -/** - *

    - * 登录请求参数 - *

    - * - * @package: com.xkcoding.rbac.security.payload - * @description: 登录请求参数 - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:52 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class LoginRequest { - - /** - * 用户名或邮箱或手机号 - */ - @NotBlank(message = "用户名不能为空") - private String usernameOrEmailOrPhone; - - /** - * 密码 - */ - @NotBlank(message = "密码不能为空") - private String password; - - /** - * 记住我 - */ - private Boolean rememberMe = false; - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java deleted file mode 100644 index bcdf201..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/payload/PageCondition.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.rbac.security.payload; - -import lombok.Data; - -/** - *

    - * 分页请求参数 - *

    - * - * @package: com.xkcoding.rbac.security.payload - * @description: 分页请求参数 - * @author: yangkai.shen - * @date: Created in 2018-12-12 18:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class PageCondition { - /** - * 当前页码 - */ - private Integer currentPage; - - /** - * 每页条数 - */ - private Integer pageSize; - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java deleted file mode 100644 index 6f7dc05..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/PermissionDao.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.model.Permission; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -/** - *

    - * 权限 DAO - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 权限 DAO - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:21 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface PermissionDao extends JpaRepository, JpaSpecificationExecutor { - - /** - * 根据角色列表查询权限列表 - * - * @param ids 角色id列表 - * @return 权限列表 - */ - @Query(value = "SELECT DISTINCT sec_permission.* FROM sec_permission,sec_role,sec_role_permission WHERE sec_role.id = sec_role_permission.role_id AND sec_permission.id = sec_role_permission.permission_id AND sec_role.id IN (:ids)", nativeQuery = true) - List selectByRoleIdList(@Param("ids") List ids); -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java deleted file mode 100644 index 0dba9e8..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RoleDao.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.model.Role; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -/** - *

    - * 角色 DAO - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 角色 DAO - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:20 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface RoleDao extends JpaRepository, JpaSpecificationExecutor { - /** - * 根据用户id 查询角色列表 - * - * @param userId 用户id - * @return 角色列表 - */ - @Query(value = "SELECT sec_role.* FROM sec_role,sec_user,sec_user_role WHERE sec_user.id = sec_user_role.user_id AND sec_role.id = sec_user_role.role_id AND sec_user.id = :userId", nativeQuery = true) - List selectByUserId(@Param("userId") Long userId); -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java deleted file mode 100644 index 21e7491..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/RolePermissionDao.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.model.RolePermission; -import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -/** - *

    - * 角色-权限 DAO - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 角色-权限 DAO - * @author: yangkai.shen - * @date: Created in 2018-12-10 13:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface RolePermissionDao extends JpaRepository, JpaSpecificationExecutor { -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java deleted file mode 100644 index 45b30e9..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.model.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -import java.util.List; -import java.util.Optional; - -/** - *

    - * 用户 DAO - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 用户 DAO - * @author: yangkai.shen - * @date: Created in 2018-12-07 16:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserDao extends JpaRepository, JpaSpecificationExecutor { - /** - * 根据用户名、邮箱、手机号查询用户 - * - * @param username 用户名 - * @param email 邮箱 - * @param phone 手机号 - * @return 用户信息 - */ - Optional findByUsernameOrEmailOrPhone(String username, String email, String phone); - - /** - * 根据用户名列表查询用户列表 - * - * @param usernameList 用户名列表 - * @return 用户列表 - */ - List findByUsernameIn(List usernameList); -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java deleted file mode 100644 index e9d1f1f..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserRoleDao.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.model.UserRole; -import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -/** - *

    - * 用户角色 DAO - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 用户角色 DAO - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface UserRoleDao extends JpaRepository, JpaSpecificationExecutor { - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java deleted file mode 100644 index e153056..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/CustomUserDetailsService.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.xkcoding.rbac.security.service; - -import com.xkcoding.rbac.security.model.Permission; -import com.xkcoding.rbac.security.model.Role; -import com.xkcoding.rbac.security.model.User; -import com.xkcoding.rbac.security.repository.PermissionDao; -import com.xkcoding.rbac.security.repository.RoleDao; -import com.xkcoding.rbac.security.repository.UserDao; -import com.xkcoding.rbac.security.vo.UserPrincipal; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * 自定义UserDetails查询 - *

    - * - * @package: com.xkcoding.rbac.security.service - * @description: 自定义UserDetails查询 - * @author: yangkai.shen - * @date: Created in 2018-12-10 10:29 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -public class CustomUserDetailsService implements UserDetailsService { - @Autowired - private UserDao userDao; - - @Autowired - private RoleDao roleDao; - - @Autowired - private PermissionDao permissionDao; - - @Override - public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException { - User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone) - .orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone)); - List roles = roleDao.selectByUserId(user.getId()); - List roleIds = roles.stream() - .map(Role::getId) - .collect(Collectors.toList()); - List permissions = permissionDao.selectByRoleIdList(roleIds); - return UserPrincipal.create(user, roles, permissions); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java deleted file mode 100644 index 739a683..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.xkcoding.rbac.security.service; - -import cn.hutool.core.util.StrUtil; -import com.google.common.collect.Lists; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.common.PageResult; -import com.xkcoding.rbac.security.model.User; -import com.xkcoding.rbac.security.payload.PageCondition; -import com.xkcoding.rbac.security.repository.UserDao; -import com.xkcoding.rbac.security.util.RedisUtil; -import com.xkcoding.rbac.security.util.SecurityUtil; -import com.xkcoding.rbac.security.vo.OnlineUser; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -/** - *

    - * 监控 Service - *

    - * - * @package: com.xkcoding.rbac.security.service - * @description: 监控 Service - * @author: yangkai.shen - * @date: Created in 2018-12-12 00:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Service -public class MonitorService { - @Autowired - private RedisUtil redisUtil; - - @Autowired - private UserDao userDao; - - /** - * 在线用户分页列表 - * - * @param pageCondition 分页参数 - * @return 在线用户分页列表 - */ - public PageResult onlineUser(PageCondition pageCondition) { - PageResult keys = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, pageCondition.getCurrentPage(), pageCondition.getPageSize()); - List rows = keys.getRows(); - Long total = keys.getTotal(); - - // 根据 redis 中键获取用户名列表 - List usernameList = rows.stream() - .map(s -> StrUtil.subAfter(s, Consts.REDIS_JWT_KEY_PREFIX, true)) - .collect(Collectors.toList()); - // 根据用户名查询用户信息 - List userList = userDao.findByUsernameIn(usernameList); - - // 封装在线用户信息 - List onlineUserList = Lists.newArrayList(); - userList.forEach(user -> onlineUserList.add(OnlineUser.create(user))); - - return new PageResult<>(onlineUserList, total); - } - - /** - * 踢出在线用户 - * - * @param names 用户名列表 - */ - public void kickout(List names) { - // 清除 Redis 中的 JWT 信息 - List redisKeys = names.parallelStream() - .map(s -> Consts.REDIS_JWT_KEY_PREFIX + s) - .collect(Collectors.toList()); - redisUtil.delete(redisKeys); - - // 获取当前用户名 - String currentUsername = SecurityUtil.getCurrentUsername(); - names.parallelStream() - .forEach(name -> { - // TODO: 通知被踢出的用户已被当前登录用户踢出, - // 后期考虑使用 websocket 实现,具体伪代码实现如下。 - // String message = "您已被用户【" + currentUsername + "】手动下线!"; - log.debug("用户【{}】被用户【{}】手动下线!", name, currentUsername); - }); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java deleted file mode 100644 index 305279c..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.common.Status; -import com.xkcoding.rbac.security.config.JwtConfig; -import com.xkcoding.rbac.security.exception.SecurityException; -import com.xkcoding.rbac.security.vo.UserPrincipal; -import io.jsonwebtoken.*; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - *

    - * JWT 工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: JWT 工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-07 13:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@EnableConfigurationProperties(JwtConfig.class) -@Configuration -@Slf4j -public class JwtUtil { - @Autowired - private JwtConfig jwtConfig; - - @Autowired - private StringRedisTemplate stringRedisTemplate; - - /** - * 创建JWT - * - * @param rememberMe 记住我 - * @param id 用户id - * @param subject 用户名 - * @param roles 用户角色 - * @param authorities 用户权限 - * @return JWT - */ - public String createJWT(Boolean rememberMe, Long id, String subject, List roles, Collection authorities) { - Date now = new Date(); - JwtBuilder builder = Jwts.builder() - .setId(id.toString()) - .setSubject(subject) - .setIssuedAt(now) - .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) - .claim("roles", roles) - .claim("authorities", authorities); - - // 设置过期时间 - Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); - if (ttl > 0) { - builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue())); - } - - String jwt = builder.compact(); - // 将生成的JWT保存至Redis - stringRedisTemplate.opsForValue() - .set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS); - return jwt; - } - - /** - * 创建JWT - * - * @param authentication 用户认证信息 - * @param rememberMe 记住我 - * @return JWT - */ - public String createJWT(Authentication authentication, Boolean rememberMe) { - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); - } - - /** - * 解析JWT - * - * @param jwt JWT - * @return {@link Claims} - */ - public Claims parseJWT(String jwt) { - try { - Claims claims = Jwts.parser() - .setSigningKey(jwtConfig.getKey()) - .parseClaimsJws(jwt) - .getBody(); - - String username = claims.getSubject(); - String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username; - - // 校验redis中的JWT是否存在 - Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); - if (Objects.isNull(expire) || expire <= 0) { - throw new SecurityException(Status.TOKEN_EXPIRED); - } - - // 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期 - String redisToken = stringRedisTemplate.opsForValue() - .get(redisKey); - if (!StrUtil.equals(jwt, redisToken)) { - throw new SecurityException(Status.TOKEN_OUT_OF_CTRL); - } - return claims; - } catch (ExpiredJwtException e) { - log.error("Token 已过期"); - throw new SecurityException(Status.TOKEN_EXPIRED); - } catch (UnsupportedJwtException e) { - log.error("不支持的 Token"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (MalformedJwtException e) { - log.error("Token 无效"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (SignatureException e) { - log.error("无效的 Token 签名"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } catch (IllegalArgumentException e) { - log.error("Token 参数不存在"); - throw new SecurityException(Status.TOKEN_PARSE_ERROR); - } - } - - /** - * 设置JWT过期 - * - * @param request 请求 - */ - public void invalidateJWT(HttpServletRequest request) { - String jwt = getJwtFromRequest(request); - String username = getUsernameFromJWT(jwt); - // 从redis中清除JWT - stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username); - } - - /** - * 根据 jwt 获取用户名 - * - * @param jwt JWT - * @return 用户名 - */ - public String getUsernameFromJWT(String jwt) { - Claims claims = parseJWT(jwt); - return claims.getSubject(); - } - - /** - * 从 request 的 header 中获取 JWT - * - * @param request 请求 - * @return JWT - */ - public String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java deleted file mode 100644 index fecd3c3..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/PageUtil.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.payload.PageCondition; -import org.springframework.data.domain.PageRequest; - -/** - *

    - * 分页工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: 分页工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-12 18:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class PageUtil { - /** - * 校验分页参数,为NULL,设置分页参数默认值 - * - * @param condition 查询参数 - * @param clazz 类 - * @param {@link PageCondition} - */ - public static void checkPageCondition(T condition, Class clazz) { - if (ObjectUtil.isNull(condition)) { - condition = ReflectUtil.newInstance(clazz); - } - // 校验分页参数 - if (ObjectUtil.isNull(condition.getCurrentPage())) { - condition.setCurrentPage(Consts.DEFAULT_CURRENT_PAGE); - } - if (ObjectUtil.isNull(condition.getPageSize())) { - condition.setPageSize(Consts.DEFAULT_PAGE_SIZE); - } - } - - /** - * 根据分页参数构建{@link PageRequest} - * - * @param condition 查询参数 - * @param {@link PageCondition} - * @return {@link PageRequest} - */ - public static PageRequest ofPageRequest(T condition) { - return PageRequest.of(condition.getCurrentPage(), condition.getPageSize()); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java deleted file mode 100644 index b8e1422..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import com.google.common.collect.Lists; -import com.xkcoding.rbac.security.common.PageResult; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.RedisConnectionUtils; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Component; - -import java.util.Collection; -import java.util.List; - -/** - *

    - * Redis工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: Redis工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-11 20:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class RedisUtil { - @Autowired - private StringRedisTemplate stringRedisTemplate; - - /** - * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率 - * - * @param patternKey key格式 - * @param currentPage 当前页码 - * @param pageSize 每页条数 - * @return 分页获取指定格式key - */ - public PageResult findKeysForPage(String patternKey, int currentPage, int pageSize) { - ScanOptions options = ScanOptions.scanOptions() - .match(patternKey) - .build(); - RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); - RedisConnection rc = factory.getConnection(); - Cursor cursor = rc.scan(options); - - List result = Lists.newArrayList(); - - long tmpIndex = 0; - int startIndex = (currentPage - 1) * pageSize; - int end = currentPage * pageSize; - while (cursor.hasNext()) { - String key = new String(cursor.next()); - if (tmpIndex >= startIndex && tmpIndex < end) { - result.add(key); - } - tmpIndex++; - } - - try { - cursor.close(); - RedisConnectionUtils.releaseConnection(rc, factory); - } catch (Exception e) { - log.warn("Redis连接关闭异常,", e); - } - - return new PageResult<>(result, tmpIndex); - } - - /** - * 删除 Redis 中的某个key - * - * @param key 键 - */ - public void delete(String key) { - stringRedisTemplate.delete(key); - } - - /** - * 批量删除 Redis 中的某些key - * - * @param keys 键列表 - */ - public void delete(Collection keys) { - stringRedisTemplate.delete(keys); - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java deleted file mode 100644 index 4987ecd..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/ResponseUtil.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.xkcoding.rbac.security.common.ApiResponse; -import com.xkcoding.rbac.security.common.BaseException; -import com.xkcoding.rbac.security.common.IStatus; -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - *

    - * Response 通用工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: Response 通用工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-07 17:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class ResponseUtil { - - /** - * 往 response 写出 json - * - * @param response 响应 - * @param status 状态 - * @param data 返回数据 - */ - public static void renderJson(HttpServletResponse response, IStatus status, Object data) { - try { - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "*"); - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(200); - - // FIXME: hutool 的 BUG:JSONUtil.toJsonStr() - // 将JSON转为String的时候,忽略null值的时候转成的String存在错误 - response.getWriter() - .write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofStatus(status, data), false))); - } catch (IOException e) { - log.error("Response写出JSON异常,", e); - } - } - - /** - * 往 response 写出 json - * - * @param response 响应 - * @param exception 异常 - */ - public static void renderJson(HttpServletResponse response, BaseException exception) { - try { - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "*"); - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(200); - - // FIXME: hutool 的 BUG:JSONUtil.toJsonStr() - // 将JSON转为String的时候,忽略null值的时候转成的String存在错误 - response.getWriter() - .write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofException(exception), false))); - } catch (IOException e) { - log.error("Response写出JSON异常,", e); - } - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java deleted file mode 100644 index 402e5f8..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/SecurityUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.vo.UserPrincipal; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; - -/** - *

    - * Spring Security工具类 - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: Spring Security工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-12 18:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class SecurityUtil { - /** - * 获取当前登录用户用户名 - * - * @return 当前登录用户用户名 - */ - public static String getCurrentUsername() { - UserPrincipal currentUser = getCurrentUser(); - return ObjectUtil.isNull(currentUser) ? Consts.ANONYMOUS_NAME : currentUser.getUsername(); - } - - /** - * 获取当前登录用户信息 - * - * @return 当前登录用户信息,匿名登录时,为null - */ - public static UserPrincipal getCurrentUser() { - Object userInfo = SecurityContextHolder.getContext() - .getAuthentication() - .getPrincipal(); - if (userInfo instanceof UserDetails) { - return (UserPrincipal) userInfo; - } - return null; - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java deleted file mode 100644 index f403dd4..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/JwtResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.rbac.security.vo; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - *

    - * JWT 响应返回 - *

    - * - * @package: com.xkcoding.rbac.security.vo - * @description: JWT 响应返回 - * @author: yangkai.shen - * @date: Created in 2018-12-10 16:01 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class JwtResponse { - /** - * token 字段 - */ - private String token; - /** - * token类型 - */ - private String tokenType = "Bearer"; - - public JwtResponse(String token) { - this.token = token; - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java deleted file mode 100644 index 2d0268b..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.rbac.security.vo; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.util.StrUtil; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.model.User; -import lombok.Data; - -/** - *

    - * 在线用户 VO - *

    - * - * @package: com.xkcoding.rbac.security.vo - * @description: 在线用户 VO - * @author: yangkai.shen - * @date: Created in 2018-12-12 00:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class OnlineUser { - - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String username; - - /** - * 昵称 - */ - private String nickname; - - /** - * 手机 - */ - private String phone; - - /** - * 邮箱 - */ - private String email; - - /** - * 生日 - */ - private Long birthday; - - /** - * 性别,男-1,女-2 - */ - private Integer sex; - - public static OnlineUser create(User user) { - OnlineUser onlineUser = new OnlineUser(); - BeanUtil.copyProperties(user, onlineUser); - // 脱敏 - onlineUser.setPhone(StrUtil.hide(user.getPhone(), 3, 7)); - onlineUser.setEmail(StrUtil.hide(user.getEmail(), 1, StrUtil.indexOfIgnoreCase(user.getEmail(), Consts.SYMBOL_EMAIL))); - return onlineUser; - } -} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java deleted file mode 100644 index b244eae..0000000 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/UserPrincipal.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.xkcoding.rbac.security.vo; - -import cn.hutool.core.util.StrUtil; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.model.Permission; -import com.xkcoding.rbac.security.model.Role; -import com.xkcoding.rbac.security.model.User; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - *

    - * 自定义User - *

    - * - * @package: com.xkcoding.rbac.security.vo - * @description: 自定义User - * @author: yangkai.shen - * @date: Created in 2018-12-10 15:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class UserPrincipal implements UserDetails { - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String username; - - /** - * 密码 - */ - @JsonIgnore - private String password; - - /** - * 昵称 - */ - private String nickname; - - /** - * 手机 - */ - private String phone; - - /** - * 邮箱 - */ - private String email; - - /** - * 生日 - */ - private Long birthday; - - /** - * 性别,男-1,女-2 - */ - private Integer sex; - - /** - * 状态,启用-1,禁用-0 - */ - private Integer status; - - /** - * 创建时间 - */ - private Long createTime; - - /** - * 更新时间 - */ - private Long updateTime; - - /** - * 用户角色列表 - */ - private List roles; - - /** - * 用户权限列表 - */ - private Collection authorities; - - public static UserPrincipal create(User user, List roles, List permissions) { - List roleNames = roles.stream() - .map(Role::getName) - .collect(Collectors.toList()); - - List authorities = permissions.stream() - .filter(permission -> StrUtil.isNotBlank(permission.getPermission())) - .map(permission -> new SimpleGrantedAuthority(permission.getPermission())) - .collect(Collectors.toList()); - - return new UserPrincipal(user.getId(), user.getUsername(), user.getPassword(), user.getNickname(), user.getPhone(), user.getEmail(), user.getBirthday(), user.getSex(), user.getStatus(), user.getCreateTime(), user.getUpdateTime(), roleNames, authorities); - } - - @Override - public Collection getAuthorities() { - return authorities; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return Objects.equals(this.status, Consts.ENABLE); - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java deleted file mode 100644 index 3d27d96..0000000 --- a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Snowflake; -import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; -import com.xkcoding.rbac.security.model.*; -import com.xkcoding.rbac.security.model.unionkey.RolePermissionKey; -import com.xkcoding.rbac.security.model.unionkey.UserRoleKey; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; - -/** - *

    - * 数据初始化测试 - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: 数据初始化测试 - * @author: yangkai.shen - * @date: Created in 2018-12-10 11:26 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class DataInitTest extends SpringBootDemoRbacSecurityApplicationTests { - @Autowired - private UserDao userDao; - - @Autowired - private RoleDao roleDao; - - @Autowired - private PermissionDao permissionDao; - - @Autowired - private UserRoleDao userRoleDao; - - @Autowired - private RolePermissionDao rolePermissionDao; - - @Autowired - private Snowflake snowflake; - - @Autowired - private BCryptPasswordEncoder encoder; - - @Test - public void initTest() { - init(); - } - - private void init() { - User admin = createUser(true); - User user = createUser(false); - - Role roleAdmin = createRole(true); - Role roleUser = createRole(false); - - createUserRoleRelation(admin.getId(), roleAdmin.getId()); - createUserRoleRelation(user.getId(), roleUser.getId()); - - // 页面权限 - Permission testPagePerm = createPermission("/test", "测试页面", 1, "page:test", null, 1, 0L); - // 按钮权限 - Permission testBtnQueryPerm = createPermission("/**/test", "测试页面-查询", 2, "btn:test:query", "GET", 1, testPagePerm.getId()); - Permission testBtnPermInsert = createPermission("/**/test", "测试页面-添加", 2, "btn:test:insert", "POST", 2, testPagePerm.getId()); - - Permission monitorOnlinePagePerm = createPermission("/monitor", "监控在线用户页面", 1, "page:monitor:online", null, 2, 0L); - Permission monitorOnlineBtnQueryPerm = createPermission("/**/api/monitor/online/user", "在线用户页面-查询", 2, "btn:monitor:online:query", "GET", 1, monitorOnlinePagePerm.getId()); - Permission monitorOnlineBtnKickoutPerm = createPermission("/**/api/monitor/online/user/kickout", "在线用户页面-踢出", 2, "btn:monitor:online:kickout", "DELETE", 2, monitorOnlinePagePerm.getId()); - - createRolePermissionRelation(roleAdmin.getId(), testPagePerm.getId()); - createRolePermissionRelation(roleUser.getId(), testPagePerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), testBtnQueryPerm.getId()); - createRolePermissionRelation(roleUser.getId(), testBtnQueryPerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), testBtnPermInsert.getId()); - createRolePermissionRelation(roleAdmin.getId(), monitorOnlinePagePerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), monitorOnlineBtnQueryPerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), monitorOnlineBtnKickoutPerm.getId()); - } - - private void createRolePermissionRelation(Long roleId, Long permissionId) { - RolePermission adminPage = new RolePermission(); - RolePermissionKey adminPageKey = new RolePermissionKey(); - adminPageKey.setRoleId(roleId); - adminPageKey.setPermissionId(permissionId); - adminPage.setId(adminPageKey); - rolePermissionDao.save(adminPage); - } - - private Permission createPermission(String url, String name, Integer type, String permission, String method, Integer sort, Long parentId) { - Permission perm = new Permission(); - perm.setId(snowflake.nextId()); - perm.setUrl(url); - perm.setName(name); - perm.setType(type); - perm.setPermission(permission); - perm.setMethod(method); - perm.setSort(sort); - perm.setParentId(parentId); - permissionDao.save(perm); - return perm; - } - - private void createUserRoleRelation(Long userId, Long roleId) { - UserRole userRole = new UserRole(); - UserRoleKey key = new UserRoleKey(); - key.setUserId(userId); - key.setRoleId(roleId); - userRole.setId(key); - userRoleDao.save(userRole); - } - - private Role createRole(boolean isAdmin) { - Role role = new Role(); - role.setId(snowflake.nextId()); - role.setName(isAdmin ? "管理员" : "普通用户"); - role.setDescription(isAdmin ? "超级管理员" : "普通用户"); - role.setCreateTime(DateUtil.current(false)); - role.setUpdateTime(DateUtil.current(false)); - roleDao.save(role); - return role; - } - - private User createUser(boolean isAdmin) { - User user = new User(); - user.setId(snowflake.nextId()); - user.setUsername(isAdmin ? "admin" : "user"); - user.setNickname(isAdmin ? "管理员" : "普通用户"); - user.setPassword(encoder.encode("123456")); - user.setBirthday(DateTime.of("1994-11-22", "yyyy-MM-dd") - .getTime()); - user.setEmail((isAdmin ? "admin" : "user") + "@xkcoding.com"); - user.setPhone(isAdmin ? "17300000000" : "17300001111"); - user.setSex(1); - user.setStatus(1); - user.setCreateTime(DateUtil.current(false)); - user.setUpdateTime(DateUtil.current(false)); - userDao.save(user); - return user; - } - -} diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java deleted file mode 100644 index 1c9bdb1..0000000 --- a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.rbac.security.repository; - -import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; -import com.xkcoding.rbac.security.model.User; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.util.Lists; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.Optional; - -/** - *

    - * UserDao 测试 - *

    - * - * @package: com.xkcoding.rbac.security.repository - * @description: UserDao 测试 - * @author: yangkai.shen - * @date: Created in 2018-12-12 01:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class UserDaoTest extends SpringBootDemoRbacSecurityApplicationTests { - @Autowired - private UserDao userDao; - - @Test - public void findByUsernameIn() { - List usernameList = Lists.newArrayList("admin", "user"); - List userList = userDao.findByUsernameIn(usernameList); - Assert.assertEquals(2, userList.size()); - log.info("【userList】= {}", userList); - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java deleted file mode 100644 index cbe51ab..0000000 --- a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.rbac.security.util; - -import cn.hutool.json.JSONUtil; -import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; -import com.xkcoding.rbac.security.common.Consts; -import com.xkcoding.rbac.security.common.PageResult; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - *

    - * 测试RedisUtil - *

    - * - * @package: com.xkcoding.rbac.security.util - * @description: 测试RedisUtil - * @author: yangkai.shen - * @date: Created in 2018-12-11 20:44 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class RedisUtilTest extends SpringBootDemoRbacSecurityApplicationTests { - @Autowired - private RedisUtil redisUtil; - - @Test - public void findKeysForPage() { - PageResult pageResult = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, 2, 1); - log.info("【pageResult】= {}", JSONUtil.toJsonStr(pageResult)); - } -} \ No newline at end of file diff --git a/spring-boot-demo-rbac-shiro/pom.xml b/spring-boot-demo-rbac-shiro/pom.xml deleted file mode 100644 index 0172141..0000000 --- a/spring-boot-demo-rbac-shiro/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-rbac-shiro - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-rbac-shiro - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - org.springframework.boot - spring-boot-starter-undertow - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.baomidou - mybatis-plus-boot-starter - 3.1.0 - - - - p6spy - p6spy - 3.8.1 - - - - - org.apache.shiro - shiro-spring-boot-starter - 1.4.0 - - - - mysql - mysql-connector-java - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-rbac-shiro - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java deleted file mode 100644 index 6159655..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/SpringBootDemoRbacShiroApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.rbac.shiro; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.rbac.shiro - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:11 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@MapperScan("com.xkcoding.rbac.shiro.mapper") -public class SpringBootDemoRbacShiroApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoRbacShiroApplication.class, args); - } -} diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java deleted file mode 100644 index cadc3c6..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/IResultCode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.rbac.shiro.common; - -/** - *

    - * 统一状态码接口 - *

    - * - * @package: com.xkcoding.rbac.shiro.common - * @description: 统一状态码接口 - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:28 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface IResultCode { - /** - * 获取状态码 - * - * @return 状态码 - */ - Integer getCode(); - - /** - * 获取返回消息 - * - * @return 返回消息 - */ - String getMessage(); -} diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java deleted file mode 100644 index 8d5e97a..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/R.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xkcoding.rbac.shiro.common; - -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - *

    - * 统一API对象返回 - *

    - * - * @package: com.xkcoding.rbac.shiro.common - * @description: 统一API对象返回 - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:24 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -public class R { - /** - * 状态码 - */ - private Integer code; - - /** - * 返回消息 - */ - private String message; - - /** - * 状态 - */ - private boolean status; - - /** - * 返回数据 - */ - private T data; - - public R(Integer code, String message, boolean status, T data) { - this.code = code; - this.message = message; - this.status = status; - this.data = data; - } - - public R(IResultCode resultCode, boolean status, T data) { - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - this.status = status; - this.data = data; - } - - public R(IResultCode resultCode, boolean status) { - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - this.status = status; - this.data = null; - } - - public static R success() { - return new R<>(ResultCode.OK, true); - } - - public static R message(String message) { - return new R<>(ResultCode.OK.getCode(), message, true, null); - } - - public static R success(T data) { - return new R<>(ResultCode.OK, true, data); - } - - public static R fail() { - return new R<>(ResultCode.ERROR, false); - } - - public static R fail(IResultCode resultCode) { - return new R<>(resultCode, false); - } - - public static R fail(Integer code, String message) { - return new R<>(code, message, false, null); - } - - public static R fail(IResultCode resultCode, T data) { - return new R<>(resultCode, false, data); - } - - public static R fail(Integer code, String message, T data) { - return new R<>(code, message, false, data); - } - -} diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java deleted file mode 100644 index 871635e..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/common/ResultCode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.rbac.shiro.common; - -import lombok.Getter; - -/** - *

    - * 通用状态枚举 - *

    - * - * @package: com.xkcoding.rbac.shiro.common - * @description: 通用状态枚举 - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:31 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Getter -public enum ResultCode implements IResultCode { - /** - * 成功 - */ - OK(200, "成功"), - /** - * 失败 - */ - ERROR(500, "失败"); - - /** - * 返回码 - */ - private Integer code; - - /** - * 返回消息 - */ - private String message; - - ResultCode(Integer code, String message) { - this.code = code; - this.message = message; - } - -} diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java deleted file mode 100644 index 3faf11a..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/config/MybatisPlusConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.rbac.shiro.config; - -import com.baomidou.mybatisplus.core.parser.ISqlParser; -import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser; -import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; -import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.ArrayList; -import java.util.List; - -/** - *

    - * MP3 配置 - *

    - * - * @package: com.xkcoding.rbac.shiro.config - * @description: MP3 配置 - * @author: yangkai.shen - * @date: Created in 2019-03-21 17:06 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class MybatisPlusConfig { - - @Bean - public PaginationInterceptor paginationInterceptor() { - PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); - - List sqlParserList = new ArrayList<>(); - // 攻击 SQL 阻断解析器、加入解析链 - sqlParserList.add(new BlockAttackSqlParser()); - paginationInterceptor.setSqlParserList(sqlParserList); - - return paginationInterceptor; - } - - /** - * SQL执行效率插件 - */ - @Bean - public PerformanceInterceptor performanceInterceptor() { - return new PerformanceInterceptor(); - } -} diff --git a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java b/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java deleted file mode 100644 index 59f6d9c..0000000 --- a/spring-boot-demo-rbac-shiro/src/main/java/com/xkcoding/rbac/shiro/controller/TestController.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.rbac.shiro.controller; - -import com.xkcoding.rbac.shiro.common.R; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 测试Controller - *

    - * - * @package: com.xkcoding.rbac.shiro.controller - * @description: 测试Controller - * @author: yangkai.shen - * @date: Created in 2019-03-21 16:13 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/test") -public class TestController { - - @GetMapping("") - public R test() { - return R.success(); - } -} diff --git a/spring-boot-demo-session/pom.xml b/spring-boot-demo-session/pom.xml deleted file mode 100644 index 494e6ad..0000000 --- a/spring-boot-demo-session/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-session - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-session - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.session - spring-session-data-redis - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-session - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java b/spring-boot-demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java deleted file mode 100644 index 2834699..0000000 --- a/spring-boot-demo-session/src/main/java/com/xkcoding/session/SpringBootDemoSessionApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.session; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.session - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018-12-19 19:35 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoSessionApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoSessionApplication.class, args); - } - -} - diff --git a/spring-boot-demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java b/spring-boot-demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java deleted file mode 100644 index 2528935..0000000 --- a/spring-boot-demo-session/src/main/java/com/xkcoding/session/config/WebMvcConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.session.config; - -import com.xkcoding.session.interceptor.SessionInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - *

    - * WebMvc 配置类 - *

    - * - * @package: com.xkcoding.session.config - * @description: WebMvc 配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-19 19:50 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - @Autowired - private SessionInterceptor sessionInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - InterceptorRegistration sessionInterceptorRegistry = registry.addInterceptor(sessionInterceptor); - // 排除不需要拦截的路径 - sessionInterceptorRegistry.excludePathPatterns("/page/login"); - sessionInterceptorRegistry.excludePathPatterns("/page/doLogin"); - sessionInterceptorRegistry.excludePathPatterns("/error"); - - // 需要拦截的路径 - sessionInterceptorRegistry.addPathPatterns("/**"); - } -} diff --git a/spring-boot-demo-session/src/main/java/com/xkcoding/session/constants/Consts.java b/spring-boot-demo-session/src/main/java/com/xkcoding/session/constants/Consts.java deleted file mode 100644 index 38cf8f3..0000000 --- a/spring-boot-demo-session/src/main/java/com/xkcoding/session/constants/Consts.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xkcoding.session.constants; - -/** - *

    - * 常量池 - *

    - * - * @package: com.xkcoding.session.constants - * @description: 常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-19 19:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface Consts { - /** - * session保存的key - */ - String SESSION_KEY = "key:session:token"; -} diff --git a/spring-boot-demo-session/src/main/java/com/xkcoding/session/controller/PageController.java b/spring-boot-demo-session/src/main/java/com/xkcoding/session/controller/PageController.java deleted file mode 100644 index d6b4756..0000000 --- a/spring-boot-demo-session/src/main/java/com/xkcoding/session/controller/PageController.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.xkcoding.session.controller; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.session.constants.Consts; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -/** - *

    - * 页面跳转 Controller - *

    - * - * @package: com.xkcoding.session.controller - * @description: 页面跳转 Controller - * @author: yangkai.shen - * @date: Created in 2018-12-19 19:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/page") -public class PageController { - /** - * 跳转到 首页 - * - * @param request 请求 - */ - @GetMapping("/index") - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - String token = (String) request.getSession().getAttribute(Consts.SESSION_KEY); - mv.setViewName("index"); - mv.addObject("token", token); - return mv; - } - - /** - * 跳转到 登录页 - * - * @param redirect 是否是跳转回来的 - */ - @GetMapping("/login") - public ModelAndView login(Boolean redirect) { - ModelAndView mv = new ModelAndView(); - - if (ObjectUtil.isNotNull(redirect) && ObjectUtil.equal(true, redirect)) { - mv.addObject("message", "请先登录!"); - } - mv.setViewName("login"); - return mv; - } - - @GetMapping("/doLogin") - public String doLogin(HttpSession session) { - session.setAttribute(Consts.SESSION_KEY, IdUtil.fastUUID()); - - return "redirect:/page/index"; - } -} diff --git a/spring-boot-demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java b/spring-boot-demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java deleted file mode 100644 index c797655..0000000 --- a/spring-boot-demo-session/src/main/java/com/xkcoding/session/interceptor/SessionInterceptor.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.session.interceptor; - -import com.xkcoding.session.constants.Consts; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -/** - *

    - * 校验Session的拦截器 - *

    - * - * @package: com.xkcoding.session.interceptor - * @description: 校验Session的拦截器 - * @author: yangkai.shen - * @date: Created in 2018-12-19 19:40 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public class SessionInterceptor extends HandlerInterceptorAdapter { - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - HttpSession session = request.getSession(); - if (session.getAttribute(Consts.SESSION_KEY) != null) { - return true; - } - // 跳转到登录页 - String url = "/page/login?redirect=true"; - response.sendRedirect(request.getContextPath() + url); - return false; - } -} diff --git a/spring-boot-demo-sharding-jdbc/README.md b/spring-boot-demo-sharding-jdbc/README.md deleted file mode 100644 index c445002..0000000 --- a/spring-boot-demo-sharding-jdbc/README.md +++ /dev/null @@ -1,295 +0,0 @@ -# spring-boot-demo-sharding-jdbc - -> 本 demo 主要演示了如何集成 `sharding-jdbc` 实现分库分表操作,ORM 层使用了`Mybatis-Plus`简化开发,童鞋们可以按照自己的喜好替换为 JPA、通用Mapper、JdbcTemplate甚至原生的JDBC都可以。 -> -> PS: -> -> 1. 目前当当官方提供的starter存在bug,版本号:`3.1.0`,因此本demo采用手动配置。 -> 2. 文档真的很垃圾​ :joy: - -## 1. 运行方式 - -1. 在数据库创建2个数据库,分别为:`spring-boot-demo`、`spring-boot-demo-2` -2. 去数据库执行 `sql/schema.sql` ,创建 `6` 张分片表 -3. 找到 `DataSourceShardingConfig` 配置类,修改 `数据源` 的相关配置,位于 `dataSourceMap()` 这个方法 -4. 找到测试类 `SpringBootDemoShardingJdbcApplicationTests` 进行测试 - -## 2. 关键代码 - -### 2.1. `pom.xml` - -```xml - - - 4.0.0 - - spring-boot-demo-sharding-jdbc - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-sharding-jdbc - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.baomidou - mybatis-plus-boot-starter - 3.1.0 - - - - mysql - mysql-connector-java - - - - io.shardingsphere - sharding-jdbc-core - 3.1.0 - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-sharding-jdbc - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 2.2. `CustomSnowflakeKeyGenerator.java` - -```java -package com.xkcoding.sharding.jdbc.config; - -import cn.hutool.core.lang.Snowflake; -import io.shardingsphere.core.keygen.KeyGenerator; - -/** - *

    - * 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 - *

    - * - * @package: com.xkcoding.sharding.jdbc.config - * @description: 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 - * @author: yangkai.shen - * @date: Created in 2019-03-26 17:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class CustomSnowflakeKeyGenerator implements KeyGenerator { - private Snowflake snowflake; - - public CustomSnowflakeKeyGenerator(Snowflake snowflake) { - this.snowflake = snowflake; - } - - @Override - public Number generateKey() { - return snowflake.nextId(); - } -} -``` - -### 2.3. `DataSourceShardingConfig.java` - -```java -/** - *

    - * sharding-jdbc 的数据源配置 - *

    - * - * @package: com.xkcoding.sharding.jdbc.config - * @description: sharding-jdbc 的数据源配置 - * @author: yangkai.shen - * @date: Created in 2019-03-26 16:47 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class DataSourceShardingConfig { - private static final Snowflake snowflake = IdUtil.createSnowflake(1, 1); - - /** - * 需要手动配置事务管理器 - */ - @Bean - public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) { - return new DataSourceTransactionManager(dataSource); - } - - @Bean(name = "dataSource") - @Primary - public DataSource dataSource() throws SQLException { - ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); - // 设置分库策略 - shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")); - // 设置规则适配的表 - shardingRuleConfig.getBindingTableGroups().add("t_order"); - // 设置分表策略 - shardingRuleConfig.getTableRuleConfigs().add(orderTableRule()); - shardingRuleConfig.setDefaultDataSourceName("ds0"); - shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration()); - - Properties properties = new Properties(); - properties.setProperty("sql.show", "true"); - - return ShardingDataSourceFactory.createDataSource(dataSourceMap(), shardingRuleConfig, new ConcurrentHashMap<>(16), properties); - } - - private TableRuleConfiguration orderTableRule() { - TableRuleConfiguration tableRule = new TableRuleConfiguration(); - // 设置逻辑表名 - tableRule.setLogicTable("t_order"); - // ds${0..1}.t_order_${0..2} 也可以写成 ds$->{0..1}.t_order_$->{0..1} - tableRule.setActualDataNodes("ds${0..1}.t_order_${0..2}"); - tableRule.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_$->{order_id % 3}")); - tableRule.setKeyGenerator(customKeyGenerator()); - tableRule.setKeyGeneratorColumnName("order_id"); - return tableRule; - } - - private Map dataSourceMap() { - Map dataSourceMap = new HashMap<>(16); - - // 配置第一个数据源 - HikariDataSource ds0 = new HikariDataSource(); - ds0.setDriverClassName("com.mysql.cj.jdbc.Driver"); - ds0.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); - ds0.setUsername("root"); - ds0.setPassword("root"); - - // 配置第二个数据源 - HikariDataSource ds1 = new HikariDataSource(); - ds1.setDriverClassName("com.mysql.cj.jdbc.Driver"); - ds1.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); - ds1.setUsername("root"); - ds1.setPassword("root"); - - dataSourceMap.put("ds0", ds0); - dataSourceMap.put("ds1", ds1); - return dataSourceMap; - } - - /** - * 自定义主键生成器 - */ - private KeyGenerator customKeyGenerator() { - return new CustomSnowflakeKeyGenerator(snowflake); - } - -} -``` - -### 2.3. `SpringBootDemoShardingJdbcApplicationTests.java` - -```java -/** - *

    - * 测试sharding-jdbc分库分表 - *

    - * - * @package: com.xkcoding.sharding.jdbc - * @description: 测试sharding-jdbc分库分表 - * @author: yangkai.shen - * @date: Created in 2019-03-26 13:44 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoShardingJdbcApplicationTests { - @Autowired - private OrderMapper orderMapper; - - /** - * 测试新增 - */ - @Test - public void testInsert() { - for (long i = 1; i < 10; i++) { - for (long j = 1; j < 20; j++) { - Order order = Order.builder().userId(i).orderId(j).remark(RandomUtil.randomString(20)).build(); - orderMapper.insert(order); - } - } - } - - /** - * 测试更新 - */ - @Test - public void testUpdate() { - Order update = new Order(); - update.setRemark("修改备注信息"); - orderMapper.update(update, Wrappers.update().lambda().eq(Order::getOrderId, 2).eq(Order::getUserId, 2)); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - orderMapper.delete(new QueryWrapper<>()); - } - - /** - * 测试查询 - */ - @Test - public void testSelect() { - List orders = orderMapper.selectList(Wrappers.query().lambda().in(Order::getOrderId, 1, 2)); - log.info("【orders】= {}", JSONUtil.toJsonStr(orders)); - } - -} -``` - -## 3. 参考 - -1. `ShardingSphere` 官网:https://shardingsphere.apache.org/index_zh.html (虽然文档确实垃圾,但是还是得参考啊~) -2. `Mybatis-Plus` 语法参考官网:https://mybatis.plus/ \ No newline at end of file diff --git a/spring-boot-demo-sharding-jdbc/pom.xml b/spring-boot-demo-sharding-jdbc/pom.xml deleted file mode 100644 index 31ee384..0000000 --- a/spring-boot-demo-sharding-jdbc/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-sharding-jdbc - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-sharding-jdbc - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.baomidou - mybatis-plus-boot-starter - 3.1.0 - - - - mysql - mysql-connector-java - - - - io.shardingsphere - sharding-jdbc-core - 3.1.0 - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-sharding-jdbc - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java b/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java deleted file mode 100644 index 897d1f6..0000000 --- a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xkcoding.sharding.jdbc; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.sharding.jdbc - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-01-23 22:05 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableTransactionManagement(proxyTargetClass = true) -@MapperScan("com.xkcoding.sharding.jdbc.mapper") -public class SpringBootDemoShardingJdbcApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoShardingJdbcApplication.class, args); - } - -} - diff --git a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java b/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java deleted file mode 100644 index 1ef1d11..0000000 --- a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/CustomSnowflakeKeyGenerator.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.sharding.jdbc.config; - -import cn.hutool.core.lang.Snowflake; -import io.shardingsphere.core.keygen.KeyGenerator; - -/** - *

    - * 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 - *

    - * - * @package: com.xkcoding.sharding.jdbc.config - * @description: 自定义雪花算法,替换 DefaultKeyGenerator,避免DefaultKeyGenerator生成的id大几率是偶数 - * @author: yangkai.shen - * @date: Created in 2019-03-26 17:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class CustomSnowflakeKeyGenerator implements KeyGenerator { - private Snowflake snowflake; - - public CustomSnowflakeKeyGenerator(Snowflake snowflake) { - this.snowflake = snowflake; - } - - @Override - public Number generateKey() { - return snowflake.nextId(); - } -} diff --git a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java b/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java deleted file mode 100644 index 2236503..0000000 --- a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/config/DataSourceShardingConfig.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.xkcoding.sharding.jdbc.config; - -import cn.hutool.core.lang.Snowflake; -import cn.hutool.core.util.IdUtil; -import com.zaxxer.hikari.HikariDataSource; -import io.shardingsphere.api.config.rule.ShardingRuleConfiguration; -import io.shardingsphere.api.config.rule.TableRuleConfiguration; -import io.shardingsphere.api.config.strategy.InlineShardingStrategyConfiguration; -import io.shardingsphere.api.config.strategy.NoneShardingStrategyConfiguration; -import io.shardingsphere.core.keygen.KeyGenerator; -import io.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.jdbc.datasource.DataSourceTransactionManager; - -import javax.sql.DataSource; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -/** - *

    - * sharding-jdbc 的数据源配置 - *

    - * - * @package: com.xkcoding.sharding.jdbc.config - * @description: sharding-jdbc 的数据源配置 - * @author: yangkai.shen - * @date: Created in 2019-03-26 16:47 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class DataSourceShardingConfig { - private static final Snowflake snowflake = IdUtil.createSnowflake(1, 1); - - /** - * 需要手动配置事务管理器 - */ - @Bean - public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) { - return new DataSourceTransactionManager(dataSource); - } - - @Bean(name = "dataSource") - @Primary - public DataSource dataSource() throws SQLException { - ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); - // 设置分库策略 - shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}")); - // 设置规则适配的表 - shardingRuleConfig.getBindingTableGroups().add("t_order"); - // 设置分表策略 - shardingRuleConfig.getTableRuleConfigs().add(orderTableRule()); - shardingRuleConfig.setDefaultDataSourceName("ds0"); - shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration()); - - Properties properties = new Properties(); - properties.setProperty("sql.show", "true"); - - return ShardingDataSourceFactory.createDataSource(dataSourceMap(), shardingRuleConfig, new ConcurrentHashMap<>(16), properties); - } - - private TableRuleConfiguration orderTableRule() { - TableRuleConfiguration tableRule = new TableRuleConfiguration(); - // 设置逻辑表名 - tableRule.setLogicTable("t_order"); - // ds${0..1}.t_order_${0..2} 也可以写成 ds$->{0..1}.t_order_$->{0..1} - tableRule.setActualDataNodes("ds${0..1}.t_order_${0..2}"); - tableRule.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_$->{order_id % 3}")); - tableRule.setKeyGenerator(customKeyGenerator()); - tableRule.setKeyGeneratorColumnName("order_id"); - return tableRule; - } - - private Map dataSourceMap() { - Map dataSourceMap = new HashMap<>(16); - - // 配置第一个数据源 - HikariDataSource ds0 = new HikariDataSource(); - ds0.setDriverClassName("com.mysql.cj.jdbc.Driver"); - ds0.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); - ds0.setUsername("root"); - ds0.setPassword("root"); - - // 配置第二个数据源 - HikariDataSource ds1 = new HikariDataSource(); - ds1.setDriverClassName("com.mysql.cj.jdbc.Driver"); - ds1.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8"); - ds1.setUsername("root"); - ds1.setPassword("root"); - - dataSourceMap.put("ds0", ds0); - dataSourceMap.put("ds1", ds1); - return dataSourceMap; - } - - /** - * 自定义主键生成器 - */ - private KeyGenerator customKeyGenerator() { - return new CustomSnowflakeKeyGenerator(snowflake); - } - -} diff --git a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java b/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java deleted file mode 100644 index d90027c..0000000 --- a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/mapper/OrderMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.sharding.jdbc.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.xkcoding.sharding.jdbc.model.Order; -import org.springframework.stereotype.Component; - -/** - *

    - * 订单表 Mapper - *

    - * - * @package: com.xkcoding.sharding.jdbc.mapper - * @description: 订单表 Mapper - * @author: yangkai.shen - * @date: Created in 2019-03-26 13:38 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface OrderMapper extends BaseMapper { -} diff --git a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java b/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java deleted file mode 100644 index 2429308..0000000 --- a/spring-boot-demo-sharding-jdbc/src/main/java/com/xkcoding/sharding/jdbc/model/Order.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.sharding.jdbc.model; - -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - *

    - * 订单表 - *

    - * - * @package: com.xkcoding.sharding.jdbc.model - * @description: 订单表 - * @author: yangkai.shen - * @date: Created in 2019-03-26 13:35 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -@TableName(value = "t_order") -public class Order { - /** - * 主键 - */ - private Long id; - /** - * 用户id - */ - private Long userId; - - /** - * 订单id - */ - private Long orderId; - /** - * 备注 - */ - private String remark; -} diff --git a/spring-boot-demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java b/spring-boot-demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java deleted file mode 100644 index a74c199..0000000 --- a/spring-boot-demo-sharding-jdbc/src/test/java/com/xkcoding/sharding/jdbc/SpringBootDemoShardingJdbcApplicationTests.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.xkcoding.sharding.jdbc; - -import cn.hutool.core.util.RandomUtil; -import cn.hutool.json.JSONUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.xkcoding.sharding.jdbc.mapper.OrderMapper; -import com.xkcoding.sharding.jdbc.model.Order; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.List; - -/** - *

    - * 测试sharding-jdbc分库分表 - *

    - * - * @package: com.xkcoding.sharding.jdbc - * @description: 测试sharding-jdbc分库分表 - * @author: yangkai.shen - * @date: Created in 2019-03-26 13:44 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoShardingJdbcApplicationTests { - @Autowired - private OrderMapper orderMapper; - - /** - * 测试新增 - */ - @Test - public void testInsert() { - for (long i = 1; i < 10; i++) { - for (long j = 1; j < 20; j++) { - Order order = Order.builder().userId(i).orderId(j).remark(RandomUtil.randomString(20)).build(); - orderMapper.insert(order); - } - } - } - - /** - * 测试更新 - */ - @Test - public void testUpdate() { - Order update = new Order(); - update.setRemark("修改备注信息"); - orderMapper.update(update, Wrappers.update().lambda().eq(Order::getOrderId, 2).eq(Order::getUserId, 2)); - } - - /** - * 测试删除 - */ - @Test - public void testDelete() { - orderMapper.delete(new QueryWrapper<>()); - } - - /** - * 测试查询 - */ - @Test - public void testSelect() { - List orders = orderMapper.selectList(Wrappers.query().lambda().in(Order::getOrderId, 1, 2)); - log.info("【orders】= {}", JSONUtil.toJsonStr(orders)); - } - -} - diff --git a/spring-boot-demo-social/README.md b/spring-boot-demo-social/README.md deleted file mode 100644 index 6355ef6..0000000 --- a/spring-boot-demo-social/README.md +++ /dev/null @@ -1,500 +0,0 @@ -# spring-boot-demo-social - -> 此 demo 主要演示 Spring Boot 项目如何使用 **[史上最全的第三方登录工具 - JustAuth](https://github.com/zhangyd-c/JustAuth)** 实现第三方登录,包括QQ登录、GitHub登录、微信登录、谷歌登录、微软登录、小米登录、企业微信登录。 -> -> 通过 [justauth-spring-boot-starter](https://search.maven.org/artifact/com.xkcoding/justauth-spring-boot-starter) 快速集成,好嗨哟~ -> -> JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具类库**,它可以让我们脱离繁琐的第三方登录SDK,让登录变得**So easy!** -> -> 1. **全**:已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划! ->2. **简**:API就是奔着最简单去设计的(见后面[`快速开始`](https://github.com/zhangyd-c/JustAuth#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)),尽量让您用起来没有障碍感! -> ->PS: 本人十分幸运的参与到了这个SDK的开发,主要开发了**QQ登录、微信登录、小米登录、微软登录、谷歌登录**这 **`5`** 个第三方登录,以及一些BUG的修复工作。再次感谢 [@母狼](https://github.com/zhangyd-c) 开源这个又好用又全面的第三方登录SDK。 - -如果技术选型是 `JFinal` 的,请查看此 [**`demo`**](https://github.com/xkcoding/jfinal-justauth-demo) - -https://github.com/xkcoding/jfinal-justauth-demo - -如果技术选型是 `ActFramework` 的,请查看此 [**`demo`**](https://github.com/xkcoding/act-justauth-demo) - -https://github.com/xkcoding/act-justauth-demo - -## 1. 环境准备 - -### 1.1. 公网服务器准备 - -首先准备一台有公网IP的服务器,可以选用阿里云或者腾讯云,如果选用的是阿里云的,可以使用我的[优惠链接](https://chuangke.aliyun.com/invite?userCode=r8z5amhr)购买。 - -### 1.2. 内网穿透frp搭建 - -> frp 安装程序:https://github.com/fatedier/frp/releases - -#### 1.2.1. frp服务端搭建 - -服务端搭建在上一步准备的公网服务器上,因为服务器是centos7 x64的系统,因此,这里下载安装包版本为linux_amd64的 [frp_0.27.0_linux_amd64.tar.gz](https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz) 。 - -1. 下载安装包 - - ```shell - $ wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz - ``` - -2. 解压安装包 - - ```shell - $ tar -zxvf frp_0.27.0_linux_amd64.tar.gz - ``` - -3. 修改配置文件 - - ```shell - $ cd frp_0.27.0_linux_amd64 - $ vim frps.ini - - [common] - bind_port = 7100 - vhost_http_port = 7200 - ``` - -4. 启动frp服务端 - - ```shell - $ ./frps -c frps.ini - 2019/06/15 16:42:02 [I] [service.go:139] frps tcp listen on 0.0.0.0:7100 - 2019/06/15 16:42:02 [I] [service.go:181] http service listen on 0.0.0.0:7200 - 2019/06/15 16:42:02 [I] [root.go:204] Start frps success - ``` - -#### 1.2.2. frp客户端搭建 - -客户端搭建在本地的Mac上,因此下载安装包版本为darwin_amd64的 [frp_0.27.0_darwin_amd64.tar.gz](https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_darwin_amd64.tar.gz) 。 - -1. 下载安装包 - - ```shell - $ wget https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_darwin_amd64.tar.gz - ``` - -2. 解压安装包 - - ```shell - $ tar -zxvf frp_0.27.0_darwin_amd64.tar.gz - ``` - -3. 修改配置文件,配置服务端ip端口及监听的域名信息 - - ```shell - $ cd frp_0.27.0_darwin_amd64 - $ vim frpc.ini - - [common] - server_addr = 120.92.169.103 - server_port = 7100 - - [web] - type = http - local_port = 8080 - custom_domains = oauth.xkcoding.com - ``` - -4. 启动frp客户端 - - ```shell - $ ./frpc -c frpc.ini - 2019/06/15 16:48:52 [I] [service.go:221] login to server success, get run id [8bb83bae5c58afe6], server udp port [0] - 2019/06/15 16:48:52 [I] [proxy_manager.go:137] [8bb83bae5c58afe6] proxy added: [web] - 2019/06/15 16:48:52 [I] [control.go:144] [web] start proxy success - ``` - -### 1.3. 配置域名解析 - -前往阿里云DNS解析,将域名解析到我们的公网服务器上,比如我的就是将 `oauth.xkcoding.com -> 120.92.169.103` - -![image-20190615165843639](http://static.xkcoding.com/spring-boot-demo/social/063923.jpg) - -### 1.4. nginx代理 - -nginx 的搭建就不在此赘述了,只说配置 - -```nginx -server { - listen 80; - server_name oauth.xkcoding.com; - - location / { - proxy_pass http://127.0.0.1:7200; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_buffering off; - sendfile off; - proxy_max_temp_file_size 0; - client_max_body_size 10m; - client_body_buffer_size 128k; - proxy_connect_timeout 90; - proxy_send_timeout 90; - proxy_read_timeout 90; - proxy_temp_file_write_size 64k; - proxy_http_version 1.1; - proxy_request_buffering off; - } -} -``` - -测试配置文件是否有问题 - -```shell -$ nginx -t -nginx: the configuration file /etc/nginx/nginx.conf syntax is ok -nginx: configuration file /etc/nginx/nginx.conf test is successful -``` - -重新加载配置文件,使其生效 - -```shell -$ nginx -s reload -``` - -> 现在当我们在浏览器输入 `oauth.xkcoding.com` 的时候,网络流量其实会经历以下几个步骤: -> -> 1. 通过之前配的DNS域名解析会访问到我们的公网服务器 `120.92.169.103` 的 80 端口 -> 2. 再经过 nginx,代理到本地的 7200 端口 -> 3. 再经过 frp 穿透到我们的 Mac 电脑的 8080 端口 -> 4. 此时 8080 就是我们的应用程序端口 - -### 1.5. 第三方平台申请 - -#### 1.5.1. QQ互联平台申请 - -1. 前往 https://connect.qq.com/ -2. 申请开发者 -3. 应用管理 -> 添加网站应用,等待审核通过即可 - -![image-20190617144655429](http://static.xkcoding.com/spring-boot-demo/social/063921-1.jpg) - -#### 1.5.2. GitHub平台申请 - -1. 前往 https://github.com/settings/developers -2. 点击 `New OAuth App` 按钮创建应用 - -![image-20190617145839851](http://static.xkcoding.com/spring-boot-demo/social/063919.jpg) - -#### 1.5.3 微信开放平台申请 - -这里微信开放平台需要用企业的,个人没有资质,所以我在某宝租了一个月的资质,需要的可以 [戳我租赁](https://item.taobao.com/item.htm?spm=2013.1.w4023-5034755838.13.747a61a7ccfHwS&id=554942413474) - -> 声明:本人与该店铺无利益相关,纯属个人觉得好用做分享 -> -> 该店铺有两种方式: -> -> 1. 店铺支持帮你过企业资质,这里就用你自己的开放平台号就好了 -> 2. 临时使用可以问店家租一个月进行开发,这里租了之后,店家会把 AppID 和 AppSecret 的信息发给你,你提供回调域就好了 - -因此这里我就贴出一张授权回调的地址作参考。 - -![image-20190617153552218](http://static.xkcoding.com/spring-boot-demo/social/063927-1.jpg) - -#### 1.5.4. 谷歌开放平台申请 - -1. 前往 https://console.developers.google.com/projectcreate 创建项目 -2. 前往 https://console.developers.google.com/apis/credentials ,在第一步创建的项目下,添加应用 - -![image-20190617151119584](http://static.xkcoding.com/spring-boot-demo/social/063920.jpg) - -![image-20190617150903039](http://static.xkcoding.com/spring-boot-demo/social/063922.jpg) - -#### 1.5.5. 微软开放平台申请 - -1. 前往 https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade 注册应用 -2. 在注册应用的时候就需要填写回调地址,当然后期也可以重新修改 - -![image-20190617152529449](http://static.xkcoding.com/spring-boot-demo/social/063921.jpg) - -3. client id 在这里 - -![image-20190617152805581](http://static.xkcoding.com/spring-boot-demo/social/063927.jpg) - -4. client secret 需要自己在这里生成 - -![image-20190617152711938](http://static.xkcoding.com/spring-boot-demo/social/063924.jpg) - -#### 1.5.6. 小米开放平台申请 - -1. 申请小米开发者,审核通过 -2. 前往 https://dev.mi.com/passport/oauth2/applist 添加oauth应用,选择 `创建网页应用` -3. 填写基本信息之后,进入应用信息页面填写 `回调地址` - -![image-20190617151502414](http://static.xkcoding.com/spring-boot-demo/social/063924-1.jpg) - -4. 应用审核通过之后,可以在应用信息页面的 `应用详情` 查看到 AppKey 和 AppSecret,吐槽下,小米应用的审核速度特别慢,需要耐心等待。。。。 - -![image-20190617151624603](http://static.xkcoding.com/spring-boot-demo/social/063926.jpg) - -#### 1.5.7. 企业微信平台申请 - -> 参考:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html - -## 2. 主要代码 - -> 本 demo 采用 Redis 缓存 state,所以请准备 Redis 环境,如果没有 Redis 环境,可以将配置文件的缓存配置为 -> -> ```yaml -> justauth: -> cache: -> type: default -> ``` - -### 2.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-social - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-social - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.0 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - - com.xkcoding - justauth-spring-boot-starter - ${justauth-spring-boot.version} - - - - org.projectlombok - lombok - true - - - - com.google.guava - guava - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-social - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 2.2. application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo - -spring: - redis: - host: localhost - # 连接超时时间(记得添加单位,Duration) - timeout: 10000ms - # Redis默认情况下有16个分片,这里配置具体使用的分片 - # database: 0 - lettuce: - pool: - # 连接池最大连接数(使用负值表示没有限制) 默认 8 - max-active: 8 - # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 - max-wait: -1ms - # 连接池中的最大空闲连接 默认 8 - max-idle: 8 - # 连接池中的最小空闲连接 默认 0 - min-idle: 0 - cache: - # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 - type: redis - -justauth: - enabled: true - type: - qq: - client-id: 10******85 - client-secret: 1f7d************************d629e - redirect-uri: http://oauth.xkcoding.com/demo/oauth/qq/callback - github: - client-id: 2d25******d5f01086 - client-secret: 5a2919b************************d7871306d1 - redirect-uri: http://oauth.xkcoding.com/demo/oauth/github/callback - wechat: - client-id: wxdcb******4ff4 - client-secret: b4e9dc************************a08ed6d - redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat/callback - google: - client-id: 716******17-6db******vh******ttj320i******userco******t.com - client-secret: 9IBorn************7-E - redirect-uri: http://oauth.xkcoding.com/demo/oauth/google/callback - microsoft: - client-id: 7bdce8******************e194ad76c1b - client-secret: Iu0zZ4************************tl9PWan_. - redirect-uri: https://oauth.xkcoding.com/demo/oauth/microsoft/callback - mi: - client-id: 288************2994 - client-secret: nFeTt89************************== - redirect-uri: http://oauth.xkcoding.com/demo/oauth/mi/callback - wechat_enterprise: - client-id: ww58******f3************fbc - client-secret: 8G6PCr00j************************rgk************AyzaPc78 - redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_enterprise/callback - agent-id: 1*******2 - cache: - type: redis - prefix: 'SOCIAL::STATE::' - timeout: 1h -``` - -### 2.3. OauthController.java - -```java -/** - *

    - * 第三方登录 Controller - *

    - * - * @package: com.xkcoding.oauth.controller - * @description: 第三方登录 Controller - * @author: yangkai.shen - * @date: Created in 2019-05-17 10:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RestController -@RequestMapping("/oauth") -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class OauthController { - private final AuthRequestFactory factory; - - /** - * 登录类型 - */ - @GetMapping - public Map loginType() { - List oauthList = factory.oauthList(); - return oauthList.stream().collect(Collectors.toMap(oauth -> oauth.toLowerCase() + "登录", oauth -> "http://oauth.xkcoding.com/demo/oauth/login/" + oauth.toLowerCase())); - } - - /** - * 登录 - * - * @param oauthType 第三方登录类型 - * @param response response - * @throws IOException - */ - @RequestMapping("/login/{oauthType}") - public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { - AuthRequest authRequest = factory.get(getAuthSource(oauthType)); - response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState())); - } - - /** - * 登录成功后的回调 - * - * @param oauthType 第三方登录类型 - * @param callback 携带返回的信息 - * @return 登录成功后的信息 - */ - @RequestMapping("/{oauthType}/callback") - public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { - AuthRequest authRequest = factory.get(getAuthSource(oauthType)); - AuthResponse response = authRequest.login(callback); - log.info("【response】= {}", JSONUtil.toJsonStr(response)); - return response; - } - - private AuthSource getAuthSource(String type) { - if (StrUtil.isNotBlank(type)) { - return AuthSource.valueOf(type.toUpperCase()); - } else { - throw new RuntimeException("不支持的类型"); - } - } -} -``` - -### 2.4. 如果想要自定义 state 缓存 - -请看👉[这里](https://github.com/justauth/justauth-spring-boot-starter#2-%E7%BC%93%E5%AD%98%E9%85%8D%E7%BD%AE) - -## 3. 运行方式 - -打开浏览器,输入 http://oauth.xkcoding.com/demo/oauth ,点击各个登录方式自行测试。 - -> `Google 登录,有可能因为祖国的强大导致测试失败,自行解决~` :kissing_smiling_eyes: - -![image-20190809161032422](https://static.xkcoding.com/blog/2019-08-09-081033.png) - -## 参考 - -1. JustAuth 项目地址:https://github.com/justauth/JustAuth -2. justauth-spring-boot-starter 地址:https://github.com/justauth/justauth-spring-boot-starter -3. frp内网穿透项目地址:https://github.com/fatedier/frp -4. frp内网穿透官方中文文档:https://github.com/fatedier/frp/blob/master/README_zh.md -5. Frp实现内网穿透:https://zhuanlan.zhihu.com/p/45445979 -6. QQ互联文档:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0 -7. 微信开放平台文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN -8. GitHub第三方登录文档:https://developer.github.com/apps/building-oauth-apps/ -9. 谷歌Oauth2文档:https://developers.google.com/identity/protocols/OpenIDConnect -10. 微软Oauth2文档:https://docs.microsoft.com/zh-cn/graph/auth-v2-user -11. 小米开放平台账号服务文档:https://dev.mi.com/console/doc/detail?pId=707 - - - diff --git a/spring-boot-demo-social/pom.xml b/spring-boot-demo-social/pom.xml deleted file mode 100644 index 241acce..0000000 --- a/spring-boot-demo-social/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-social - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-social - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.0 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.apache.commons - commons-pool2 - - - - - com.xkcoding - justauth-spring-boot-starter - ${justauth-spring-boot.version} - - - - org.projectlombok - lombok - true - - - - com.google.guava - guava - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-social - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java b/spring-boot-demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java deleted file mode 100644 index f780d37..0000000 --- a/spring-boot-demo-social/src/main/java/com/xkcoding/social/controller/OauthController.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.xkcoding.social.controller; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.xkcoding.justauth.AuthRequestFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import me.zhyd.oauth.config.AuthSource; -import me.zhyd.oauth.model.AuthCallback; -import me.zhyd.oauth.model.AuthResponse; -import me.zhyd.oauth.request.AuthRequest; -import me.zhyd.oauth.utils.AuthStateUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - *

    - * 第三方登录 Controller - *

    - * - * @package: com.xkcoding.oauth.controller - * @description: 第三方登录 Controller - * @author: yangkai.shen - * @date: Created in 2019-05-17 10:07 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@RestController -@RequestMapping("/oauth") -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class OauthController { - private final AuthRequestFactory factory; - - /** - * 登录类型 - */ - @GetMapping - public Map loginType() { - List oauthList = factory.oauthList(); - return oauthList.stream().collect(Collectors.toMap(oauth -> oauth.toLowerCase() + "登录", oauth -> "http://oauth.xkcoding.com/demo/oauth/login/" + oauth.toLowerCase())); - } - - /** - * 登录 - * - * @param oauthType 第三方登录类型 - * @param response response - * @throws IOException - */ - @RequestMapping("/login/{oauthType}") - public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { - AuthRequest authRequest = factory.get(getAuthSource(oauthType)); - response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState())); - } - - /** - * 登录成功后的回调 - * - * @param oauthType 第三方登录类型 - * @param callback 携带返回的信息 - * @return 登录成功后的信息 - */ - @RequestMapping("/{oauthType}/callback") - public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { - AuthRequest authRequest = factory.get(getAuthSource(oauthType)); - AuthResponse response = authRequest.login(callback); - log.info("【response】= {}", JSONUtil.toJsonStr(response)); - return response; - } - - private AuthSource getAuthSource(String type) { - if (StrUtil.isNotBlank(type)) { - return AuthSource.valueOf(type.toUpperCase()); - } else { - throw new RuntimeException("不支持的类型"); - } - } -} diff --git a/spring-boot-demo-swagger-beauty/README.md b/spring-boot-demo-swagger-beauty/README.md deleted file mode 100644 index b63b09b..0000000 --- a/spring-boot-demo-swagger-beauty/README.md +++ /dev/null @@ -1,297 +0,0 @@ -# spring-boot-demo-swagger-beauty - -> 此 demo 主要演示如何集成第三方的 swagger 来替换原生的 swagger,美化文档样式。本 demo 使用 [swagger-spring-boot-starter](https://github.com/battcn/swagger-spring-boot) 集成。 -> -> 启动项目,访问地址:http://localhost:8080/demo/swagger-ui.html#/ -> -> 用户名:xkcoding -> -> 密码:123456 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-swagger-beauty - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-swagger-beauty - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.2-RELEASE - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.battcn - swagger-spring-boot-starter - ${battcn.swagger.version} - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-swagger-beauty - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - swagger: - enabled: true - title: spring-boot-demo - description: 这是一个简单的 Swagger API 演示 - version: 1.0.0-SNAPSHOT - contact: - name: Yangkai.Shen - email: 237497819@qq.com - url: http://xkcoding.com - # swagger扫描的基础包,默认:全扫描 - # base-package: - # 需要处理的基础URL规则,默认:/** - # base-path: - # 需要排除的URL规则,默认:空 - # exclude-path: - security: - # 是否启用 swagger 登录验证 - filter-plugin: true - username: xkcoding - password: 123456 - global-response-messages: - GET[0]: - code: 400 - message: Bad Request,一般为请求参数不对 - GET[1]: - code: 404 - message: NOT FOUND,一般为请求路径不对 - GET[2]: - code: 500 - message: ERROR,一般为程序内部错误 - POST[0]: - code: 400 - message: Bad Request,一般为请求参数不对 - POST[1]: - code: 404 - message: NOT FOUND,一般为请求路径不对 - POST[2]: - code: 500 - message: ERROR,一般为程序内部错误 -``` - -## ApiResponse.java - -```java -/** - *

    - * 通用API接口返回 - *

    - * - * @package: com.xkcoding.swagger.beauty.common - * @description: 通用API接口返回 - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "通用PI接口返回", description = "Common Api Response") -public class ApiResponse implements Serializable { - private static final long serialVersionUID = -8987146499044811408L; - /** - * 通用返回状态 - */ - @ApiModelProperty(value = "通用返回状态", required = true) - private Integer code; - /** - * 通用返回信息 - */ - @ApiModelProperty(value = "通用返回信息", required = true) - private String message; - /** - * 通用返回数据 - */ - @ApiModelProperty(value = "通用返回数据", required = true) - private T data; -} -``` - -## User.java - -```java -/** - *

    - * 用户实体 - *

    - * - * @package: com.xkcoding.swagger.beauty.entity - * @description: 用户实体 - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:13 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "用户实体", description = "User Entity") -public class User implements Serializable { - private static final long serialVersionUID = 5057954049311281252L; - /** - * 主键id - */ - @ApiModelProperty(value = "主键id", required = true) - private Integer id; - /** - * 用户名 - */ - @ApiModelProperty(value = "用户名", required = true) - private String name; - /** - * 工作岗位 - */ - @ApiModelProperty(value = "工作岗位", required = true) - private String job; -} -``` - -## UserController.java - -```java -/** - *

    - * User Controller - *

    - * - * @package: com.xkcoding.swagger.beauty.controller - * @description: User Controller - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/user") -@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") -@Slf4j -public class UserController { - @GetMapping - @ApiOperation(value = "条件查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) - public ApiResponse getByUserName(String username) { - log.info("多个参数用 @ApiImplicitParams"); - return ApiResponse.builder().code(200).message("操作成功").data(new User(1, username, "JAVA")).build(); - } - - @GetMapping("/{id}") - @ApiOperation(value = "主键查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) - public ApiResponse get(@PathVariable Integer id) { - log.info("单个参数用 @ApiImplicitParam"); - return ApiResponse.builder().code(200).message("操作成功").data(new User(id, "u1", "p1")).build(); - } - - @DeleteMapping("/{id}") - @ApiOperation(value = "删除用户(DONE)", notes = "备注") - @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) - public void delete(@PathVariable Integer id) { - log.info("单个参数用 ApiImplicitParam"); - } - - @PostMapping - @ApiOperation(value = "添加用户(DONE)") - public User post(@RequestBody User user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PostMapping("/multipar") - @ApiOperation(value = "添加用户(DONE)") - public List multipar(@RequestBody List user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - - return user; - } - - @PostMapping("/array") - @ApiOperation(value = "添加用户(DONE)") - public User[] array(@RequestBody User[] user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PutMapping("/{id}") - @ApiOperation(value = "修改用户(DONE)") - public void put(@PathVariable Long id, @RequestBody User user) { - log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); - } - - @PostMapping("/{id}/file") - @ApiOperation(value = "文件上传(DONE)") - public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { - log.info(file.getContentType()); - log.info(file.getName()); - log.info(file.getOriginalFilename()); - return file.getOriginalFilename(); - } -} -``` - -## 参考 - -- https://github.com/battcn/swagger-spring-boot/blob/master/README.md -- 几款比较好看的swagger-ui,具体使用方法参见各个依赖的官方文档: - - [battcn](https://github.com/battcn) 的 [swagger-spring-boot-starter](https://github.com/battcn/swagger-spring-boot) 文档:https://github.com/battcn/swagger-spring-boot/blob/master/README.md - - [ swagger-ui-layer](https://gitee.com/caspar-chen/Swagger-UI-layer) 文档:https://gitee.com/caspar-chen/Swagger-UI-layer#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8 - - [swagger-bootstrap-ui](https://gitee.com/xiaoym/swagger-bootstrap-ui) 文档:https://gitee.com/xiaoym/swagger-bootstrap-ui#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E - - [swagger-ui-themes](https://github.com/ostranme/swagger-ui-themes) 文档:https://github.com/ostranme/swagger-ui-themes#getting-started \ No newline at end of file diff --git a/spring-boot-demo-swagger-beauty/pom.xml b/spring-boot-demo-swagger-beauty/pom.xml deleted file mode 100644 index 947d64d..0000000 --- a/spring-boot-demo-swagger-beauty/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-swagger-beauty - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-swagger-beauty - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.2-RELEASE - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.battcn - swagger-spring-boot-starter - ${battcn.swagger.version} - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-swagger-beauty - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java b/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java deleted file mode 100644 index 6c23e90..0000000 --- a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/SpringBootDemoSwaggerBeautyApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.swagger.beauty; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.swagger.beauty - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-11-28 11:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoSwaggerBeautyApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoSwaggerBeautyApplication.class, args); - } -} diff --git a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java b/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java deleted file mode 100644 index 43ce02a..0000000 --- a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/common/ApiResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.swagger.beauty.common; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 通用API接口返回 - *

    - * - * @package: com.xkcoding.swagger.beauty.common - * @description: 通用API接口返回 - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:18 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "通用PI接口返回", description = "Common Api Response") -public class ApiResponse implements Serializable { - private static final long serialVersionUID = -8987146499044811408L; - /** - * 通用返回状态 - */ - @ApiModelProperty(value = "通用返回状态", required = true) - private Integer code; - /** - * 通用返回信息 - */ - @ApiModelProperty(value = "通用返回信息", required = true) - private String message; - /** - * 通用返回数据 - */ - @ApiModelProperty(value = "通用返回数据", required = true) - private T data; -} diff --git a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java b/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java deleted file mode 100644 index f8667d1..0000000 --- a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/controller/UserController.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.xkcoding.swagger.beauty.controller; - -import com.battcn.boot.swagger.model.DataType; -import com.battcn.boot.swagger.model.ParamType; -import com.xkcoding.swagger.beauty.common.ApiResponse; -import com.xkcoding.swagger.beauty.entity.User; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - *

    - * User Controller - *

    - * - * @package: com.xkcoding.swagger.beauty.controller - * @description: User Controller - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/user") -@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") -@Slf4j -public class UserController { - @GetMapping - @ApiOperation(value = "条件查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) - public ApiResponse getByUserName(String username) { - log.info("多个参数用 @ApiImplicitParams"); - return ApiResponse.builder().code(200).message("操作成功").data(new User(1, username, "JAVA")).build(); - } - - @GetMapping("/{id}") - @ApiOperation(value = "主键查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) - public ApiResponse get(@PathVariable Integer id) { - log.info("单个参数用 @ApiImplicitParam"); - return ApiResponse.builder().code(200).message("操作成功").data(new User(id, "u1", "p1")).build(); - } - - @DeleteMapping("/{id}") - @ApiOperation(value = "删除用户(DONE)", notes = "备注") - @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) - public void delete(@PathVariable Integer id) { - log.info("单个参数用 ApiImplicitParam"); - } - - @PostMapping - @ApiOperation(value = "添加用户(DONE)") - public User post(@RequestBody User user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PostMapping("/multipar") - @ApiOperation(value = "添加用户(DONE)") - public List multipar(@RequestBody List user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - - return user; - } - - @PostMapping("/array") - @ApiOperation(value = "添加用户(DONE)") - public User[] array(@RequestBody User[] user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PutMapping("/{id}") - @ApiOperation(value = "修改用户(DONE)") - public void put(@PathVariable Long id, @RequestBody User user) { - log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); - } - - @PostMapping("/{id}/file") - @ApiOperation(value = "文件上传(DONE)") - public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { - log.info(file.getContentType()); - log.info(file.getName()); - log.info(file.getOriginalFilename()); - return file.getOriginalFilename(); - } -} diff --git a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java b/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java deleted file mode 100644 index 758afad..0000000 --- a/spring-boot-demo-swagger-beauty/src/main/java/com/xkcoding/swagger/beauty/entity/User.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.swagger.beauty.entity; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 用户实体 - *

    - * - * @package: com.xkcoding.swagger.beauty.entity - * @description: 用户实体 - * @author: yangkai.shen - * @date: Created in 2018-11-28 14:13 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "用户实体", description = "User Entity") -public class User implements Serializable { - private static final long serialVersionUID = 5057954049311281252L; - /** - * 主键id - */ - @ApiModelProperty(value = "主键id", required = true) - private Integer id; - /** - * 用户名 - */ - @ApiModelProperty(value = "用户名", required = true) - private String name; - /** - * 工作岗位 - */ - @ApiModelProperty(value = "工作岗位", required = true) - private String job; -} diff --git a/spring-boot-demo-swagger/README.md b/spring-boot-demo-swagger/README.md deleted file mode 100644 index 960b4ae..0000000 --- a/spring-boot-demo-swagger/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# spring-boot-demo-swagger - -> 此 demo 主要演示了 Spring Boot 如何集成原生 swagger ,自动生成 API 文档。 -> -> 启动项目,访问地址:http://localhost:8080/demo/swagger-ui.html#/ - -# pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-swagger - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-swagger - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.9.2 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - io.springfox - springfox-swagger2 - ${swagger.version} - - - - io.springfox - springfox-swagger-ui - ${swagger.version} - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-swagger - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## Swagger2Config.java - -```java -/** - *

    - * Swagger2 配置 - *

    - * - * @package: com.xkcoding.swagger.config - * @description: Swagger2 配置 - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:14 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableSwagger2 -public class Swagger2Config { - - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) - .select() - .apis(RequestHandlerSelectors.basePackage("com.xkcoding.swagger.controller")) - .paths(PathSelectors.any()) - .build(); - } - - private ApiInfo apiInfo() { - return new ApiInfoBuilder().title("spring-boot-demo") - .description("这是一个简单的 Swagger API 演示") - .contact(new Contact("Yangkai.Shen", "http://xkcoding.com", "237497819@qq.com")) - .version("1.0.0-SNAPSHOT") - .build(); - } - -} -``` - -## UserController.java - -> 主要演示API层的注解。 - -```java -/** - *

    - * User Controller - *

    - * - * @package: com.xkcoding.swagger.controller - * @description: User Controller - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/user") -@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") -@Slf4j -public class UserController { - @GetMapping - @ApiOperation(value = "条件查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) - public ApiResponse getByUserName(String username) { - log.info("多个参数用 @ApiImplicitParams"); - return ApiResponse.builder().code(200) - .message("操作成功") - .data(new User(1, username, "JAVA")) - .build(); - } - - @GetMapping("/{id}") - @ApiOperation(value = "主键查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) - public ApiResponse get(@PathVariable Integer id) { - log.info("单个参数用 @ApiImplicitParam"); - return ApiResponse.builder().code(200) - .message("操作成功") - .data(new User(id, "u1", "p1")) - .build(); - } - - @DeleteMapping("/{id}") - @ApiOperation(value = "删除用户(DONE)", notes = "备注") - @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) - public void delete(@PathVariable Integer id) { - log.info("单个参数用 ApiImplicitParam"); - } - - @PostMapping - @ApiOperation(value = "添加用户(DONE)") - public User post(@RequestBody User user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PostMapping("/multipar") - @ApiOperation(value = "添加用户(DONE)") - public List multipar(@RequestBody List user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - - return user; - } - - @PostMapping("/array") - @ApiOperation(value = "添加用户(DONE)") - public User[] array(@RequestBody User[] user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PutMapping("/{id}") - @ApiOperation(value = "修改用户(DONE)") - public void put(@PathVariable Long id, @RequestBody User user) { - log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); - } - - @PostMapping("/{id}/file") - @ApiOperation(value = "文件上传(DONE)") - public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { - log.info(file.getContentType()); - log.info(file.getName()); - log.info(file.getOriginalFilename()); - return file.getOriginalFilename(); - } -} -``` - -## ApiResponse.java - -> 主要演示了 实体类 的注解。 - -```java -/** - *

    - * 通用API接口返回 - *

    - * - * @package: com.xkcoding.swagger.common - * @description: 通用API接口返回 - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "通用PI接口返回", description = "Common Api Response") -public class ApiResponse implements Serializable { - private static final long serialVersionUID = -8987146499044811408L; - /** - * 通用返回状态 - */ - @ApiModelProperty(value = "通用返回状态", required = true) - private Integer code; - /** - * 通用返回信息 - */ - @ApiModelProperty(value = "通用返回信息", required = true) - private String message; - /** - * 通用返回数据 - */ - @ApiModelProperty(value = "通用返回数据", required = true) - private T data; -} -``` - -## 参考 - -1. swagger 官方网站:https://swagger.io/ - -2. swagger 官方文档:https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Getting-started - -3. swagger 常用注解:https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations diff --git a/spring-boot-demo-swagger/pom.xml b/spring-boot-demo-swagger/pom.xml deleted file mode 100644 index 525313d..0000000 --- a/spring-boot-demo-swagger/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-swagger - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-swagger - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.9.2 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - io.springfox - springfox-swagger2 - ${swagger.version} - - - - io.springfox - springfox-swagger-ui - ${swagger.version} - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-swagger - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java deleted file mode 100644 index dbc1c78..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/SpringBootDemoSwaggerApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.swagger; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.swagger - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-11-29 13:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoSwaggerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoSwaggerApplication.class, args); - } -} diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java deleted file mode 100644 index 523a8b6..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ApiResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xkcoding.swagger.common; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 通用API接口返回 - *

    - * - * @package: com.xkcoding.swagger.common - * @description: 通用API接口返回 - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "通用PI接口返回", description = "Common Api Response") -public class ApiResponse implements Serializable { - private static final long serialVersionUID = -8987146499044811408L; - /** - * 通用返回状态 - */ - @ApiModelProperty(value = "通用返回状态", required = true) - private Integer code; - /** - * 通用返回信息 - */ - @ApiModelProperty(value = "通用返回信息", required = true) - private String message; - /** - * 通用返回数据 - */ - @ApiModelProperty(value = "通用返回数据", required = true) - private T data; -} diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java deleted file mode 100644 index 0caf4ae..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/DataType.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.swagger.common; - -/** - *

    - * 方便在 @ApiImplicitParam 的 dataType 属性使用 - *

    - * - * @package: com.xkcoding.swagger.common - * @description: 方便在 @ApiImplicitParam 的 dataType 属性使用 - * @author: yangkai.shen - * @date: Created in 2018-11-29 13:23 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public final class DataType { - - public final static String STRING = "String"; - public final static String INT = "int"; - public final static String LONG = "long"; - public final static String DOUBLE = "double"; - public final static String FLOAT = "float"; - public final static String BYTE = "byte"; - public final static String BOOLEAN = "boolean"; - public final static String ARRAY = "array"; - public final static String BINARY = "binary"; - public final static String DATETIME = "dateTime"; - public final static String PASSWORD = "password"; - -} \ No newline at end of file diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java deleted file mode 100644 index 438cea8..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/common/ParamType.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.swagger.common; - -/** - *

    - * 方便在 @ApiImplicitParam 的 paramType 属性使用 - *

    - * - * @package: com.xkcoding.swagger.common - * @description: 方便在 @ApiImplicitParam 的 paramType 属性使用 - * @author: yangkai.shen - * @date: Created in 2018-11-29 13:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public final class ParamType { - - public final static String QUERY = "query"; - public final static String HEADER = "header"; - public final static String PATH = "path"; - public final static String BODY = "body"; - public final static String FORM = "form"; - -} \ No newline at end of file diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java deleted file mode 100644 index 04d0924..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/config/Swagger2Config.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.swagger.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.Contact; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -/** - *

    - * Swagger2 配置 - *

    - * - * @package: com.xkcoding.swagger.config - * @description: Swagger2 配置 - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:14 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableSwagger2 -public class Swagger2Config { - - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) - .select() - .apis(RequestHandlerSelectors.basePackage("com.xkcoding.swagger.controller")) - .paths(PathSelectors.any()) - .build(); - } - - private ApiInfo apiInfo() { - return new ApiInfoBuilder().title("spring-boot-demo") - .description("这是一个简单的 Swagger API 演示") - .contact(new Contact("Yangkai.Shen", "http://xkcoding.com", "237497819@qq.com")) - .version("1.0.0-SNAPSHOT") - .build(); - } - -} \ No newline at end of file diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java deleted file mode 100644 index db6cd11..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/controller/UserController.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.xkcoding.swagger.controller; - -import com.xkcoding.swagger.common.ApiResponse; -import com.xkcoding.swagger.common.DataType; -import com.xkcoding.swagger.common.ParamType; -import com.xkcoding.swagger.entity.User; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - *

    - * User Controller - *

    - * - * @package: com.xkcoding.swagger.controller - * @description: User Controller - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/user") -@Api(tags = "1.0.0-SNAPSHOT", description = "用户管理", value = "用户管理") -@Slf4j -public class UserController { - @GetMapping - @ApiOperation(value = "条件查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", dataType = DataType.STRING, paramType = ParamType.QUERY, defaultValue = "xxx")}) - public ApiResponse getByUserName(String username) { - log.info("多个参数用 @ApiImplicitParams"); - return ApiResponse.builder().code(200) - .message("操作成功") - .data(new User(1, username, "JAVA")) - .build(); - } - - @GetMapping("/{id}") - @ApiOperation(value = "主键查询(DONE)", notes = "备注") - @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH)}) - public ApiResponse get(@PathVariable Integer id) { - log.info("单个参数用 @ApiImplicitParam"); - return ApiResponse.builder().code(200) - .message("操作成功") - .data(new User(id, "u1", "p1")) - .build(); - } - - @DeleteMapping("/{id}") - @ApiOperation(value = "删除用户(DONE)", notes = "备注") - @ApiImplicitParam(name = "id", value = "用户编号", dataType = DataType.INT, paramType = ParamType.PATH) - public void delete(@PathVariable Integer id) { - log.info("单个参数用 ApiImplicitParam"); - } - - @PostMapping - @ApiOperation(value = "添加用户(DONE)") - public User post(@RequestBody User user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PostMapping("/multipar") - @ApiOperation(value = "添加用户(DONE)") - public List multipar(@RequestBody List user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - - return user; - } - - @PostMapping("/array") - @ApiOperation(value = "添加用户(DONE)") - public User[] array(@RequestBody User[] user) { - log.info("如果是 POST PUT 这种带 @RequestBody 的可以不用写 @ApiImplicitParam"); - return user; - } - - @PutMapping("/{id}") - @ApiOperation(value = "修改用户(DONE)") - public void put(@PathVariable Long id, @RequestBody User user) { - log.info("如果你不想写 @ApiImplicitParam 那么 swagger 也会使用默认的参数名作为描述信息 "); - } - - @PostMapping("/{id}/file") - @ApiOperation(value = "文件上传(DONE)") - public String file(@PathVariable Long id, @RequestParam("file") MultipartFile file) { - log.info(file.getContentType()); - log.info(file.getName()); - log.info(file.getOriginalFilename()); - return file.getOriginalFilename(); - } -} diff --git a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java b/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java deleted file mode 100644 index 3862b9b..0000000 --- a/spring-boot-demo-swagger/src/main/java/com/xkcoding/swagger/entity/User.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.swagger.entity; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - *

    - * 用户实体 - *

    - * - * @package: com.xkcoding.swagger.entity - * @description: 用户实体 - * @author: yangkai.shen - * @date: Created in 2018-11-29 11:31 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "用户实体", description = "User Entity") -public class User implements Serializable { - private static final long serialVersionUID = 5057954049311281252L; - /** - * 主键id - */ - @ApiModelProperty(value = "主键id", required = true) - private Integer id; - /** - * 用户名 - */ - @ApiModelProperty(value = "用户名", required = true) - private String name; - /** - * 工作岗位 - */ - @ApiModelProperty(value = "工作岗位", required = true) - private String job; -} diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_db2.sql b/spring-boot-demo-task-quartz/init/dbTables/tables_db2.sql deleted file mode 100644 index f56ddda..0000000 --- a/spring-boot-demo-task-quartz/init/dbTables/tables_db2.sql +++ /dev/null @@ -1,148 +0,0 @@ -# -# Thanks to Horia Muntean for submitting this.... -# -# .. known to work with DB2 7.1 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" -# .. likely to work with others... -# -# In your Quartz properties file, you'll need to set -# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate -# -# If you're using DB2 6.x you'll want to set this property to -# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate -# -# Note that the blob column size (e.g. blob(2000)) dictates the amount of data that can be stored in -# that blob - i.e. limits the amount of data you can put into your JobDataMap -# - - -create table qrtz_job_details ( - sched_name varchar(120) not null, - job_name varchar(80) not null, - job_group varchar(80) not null, - description varchar(120) null, - job_class_name varchar(128) not null, - is_durable varchar(1) not null, - is_nonconcurrent varchar(1) not null, - is_update_data varchar(1) not null, - requests_recovery varchar(1) not null, - job_data blob(2000), - primary key (sched_name,job_name,job_group) -) - -create table qrtz_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - job_name varchar(80) not null, - job_group varchar(80) not null, - description varchar(120) null, - next_fire_time bigint, - prev_fire_time bigint, - priority integer, - trigger_state varchar(16) not null, - trigger_type varchar(8) not null, - start_time bigint not null, - end_time bigint, - calendar_name varchar(80), - misfire_instr smallint, - job_data blob(2000), - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) -) - -create table qrtz_simple_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - repeat_count bigint not null, - repeat_interval bigint not null, - times_triggered bigint not null, - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -) - -create table qrtz_cron_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - cron_expression varchar(120) not null, - time_zone_id varchar(80), - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -) - -CREATE TABLE qrtz_simprop_triggers - ( - sched_name varchar(120) not null, - TRIGGER_NAME VARCHAR(200) NOT NULL, - TRIGGER_GROUP VARCHAR(200) NOT NULL, - STR_PROP_1 VARCHAR(512) NULL, - STR_PROP_2 VARCHAR(512) NULL, - STR_PROP_3 VARCHAR(512) NULL, - INT_PROP_1 INT NULL, - INT_PROP_2 INT NULL, - LONG_PROP_1 BIGINT NULL, - LONG_PROP_2 BIGINT NULL, - DEC_PROP_1 NUMERIC(13,4) NULL, - DEC_PROP_2 NUMERIC(13,4) NULL, - BOOL_PROP_1 VARCHAR(1) NULL, - BOOL_PROP_2 VARCHAR(1) NULL, - PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) -) - -create table qrtz_blob_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - blob_data blob(2000) null, - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -) - -create table qrtz_calendars( - sched_name varchar(120) not null, - calendar_name varchar(80) not null, - calendar blob(2000) not null, - primary key (sched_name,calendar_name) -) - -create table qrtz_fired_triggers( - sched_name varchar(120) not null, - entry_id varchar(95) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - instance_name varchar(80) not null, - fired_time bigint not null, - sched_time bigint not null, - priority integer not null, - state varchar(16) not null, - job_name varchar(80) null, - job_group varchar(80) null, - is_nonconcurrent varchar(1) null, - requests_recovery varchar(1) null, - primary key (sched_name,entry_id) -); - - -create table qrtz_paused_trigger_grps( - sched_name varchar(120) not null, - trigger_group varchar(80) not null, - primary key (sched_name,trigger_group) -); - -create table qrtz_scheduler_state ( - sched_name varchar(120) not null, - instance_name varchar(80) not null, - last_checkin_time bigint not null, - checkin_interval bigint not null, - primary key (sched_name,instance_name) -); - -create table qrtz_locks - ( - sched_name varchar(120) not null, - lock_name varchar(40) not null, - primary key (sched_name,lock_name) -); diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_db2_v72.sql b/spring-boot-demo-task-quartz/init/dbTables/tables_db2_v72.sql deleted file mode 100644 index 2be28e0..0000000 --- a/spring-boot-demo-task-quartz/init/dbTables/tables_db2_v72.sql +++ /dev/null @@ -1,163 +0,0 @@ --- --- Thanks to Horia Muntean for submitting this, Mikkel Heisterberg for updating it --- --- .. known to work with DB2 7.2 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver" --- .. likely to work with others... --- --- In your Quartz properties file, you'll need to set --- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v7Delegate --- --- or --- --- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate --- --- If you're using DB2 6.x you'll want to set this property to --- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate --- --- Note that the blob column size (e.g. blob(2000)) dictates the amount of data that can be stored in --- that blob - i.e. limits the amount of data you can put into your JobDataMap --- - -DROP TABLE QRTZ_FIRED_TRIGGERS; -DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS; -DROP TABLE QRTZ_SCHEDULER_STATE; -DROP TABLE QRTZ_LOCKS; -DROP TABLE QRTZ_SIMPLE_TRIGGERS; -DROP TABLE QRTZ_SIMPROP_TRIGGERS; -DROP TABLE QRTZ_CRON_TRIGGERS; -DROP TABLE QRTZ_TRIGGERS; -DROP TABLE QRTZ_JOB_DETAILS; -DROP TABLE QRTZ_CALENDARS; -DROP TABLE QRTZ_BLOB_TRIGGERS; - -create table qrtz_job_details ( - sched_name varchar(120) not null, - job_name varchar(80) not null, - job_group varchar(80) not null, - description varchar(120), - job_class_name varchar(128) not null, - is_durable varchar(1) not null, - is_nonconcurrent varchar(1) not null, - is_update_data varchar(1) not null, - requests_recovery varchar(1) not null, - job_data blob(2000), - primary key (sched_name,job_name,job_group) -); - -create table qrtz_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - job_name varchar(80) not null, - job_group varchar(80) not null, - description varchar(120), - next_fire_time bigint, - prev_fire_time bigint, - priority integer, - trigger_state varchar(16) not null, - trigger_type varchar(8) not null, - start_time bigint not null, - end_time bigint, - calendar_name varchar(80), - misfire_instr smallint, - job_data blob(2000), - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,job_name,job_group) references qrtz_job_details(sched_name,job_name,job_group) -); - -create table qrtz_simple_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - repeat_count bigint not null, - repeat_interval bigint not null, - times_triggered bigint not null, - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -); - -create table qrtz_cron_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - cron_expression varchar(120) not null, - time_zone_id varchar(80), - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -); - -CREATE TABLE qrtz_simprop_triggers - ( - sched_name varchar(120) not null, - TRIGGER_NAME VARCHAR(200) NOT NULL, - TRIGGER_GROUP VARCHAR(200) NOT NULL, - STR_PROP_1 VARCHAR(512) NULL, - STR_PROP_2 VARCHAR(512) NULL, - STR_PROP_3 VARCHAR(512) NULL, - INT_PROP_1 INT NULL, - INT_PROP_2 INT NULL, - LONG_PROP_1 BIGINT NULL, - LONG_PROP_2 BIGINT NULL, - DEC_PROP_1 NUMERIC(13,4) NULL, - DEC_PROP_2 NUMERIC(13,4) NULL, - BOOL_PROP_1 VARCHAR(1) NULL, - BOOL_PROP_2 VARCHAR(1) NULL, - PRIMARY KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (sched_name,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(sched_name,TRIGGER_NAME,TRIGGER_GROUP) -); - -create table qrtz_blob_triggers( - sched_name varchar(120) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - blob_data blob(2000), - primary key (sched_name,trigger_name,trigger_group), - foreign key (sched_name,trigger_name,trigger_group) references qrtz_triggers(sched_name,trigger_name,trigger_group) -); - -create table qrtz_calendars( - sched_name varchar(120) not null, - calendar_name varchar(80) not null, - calendar blob(2000) not null, - primary key (sched_name,calendar_name) -); - -create table qrtz_fired_triggers( - sched_name varchar(120) not null, - entry_id varchar(95) not null, - trigger_name varchar(80) not null, - trigger_group varchar(80) not null, - instance_name varchar(80) not null, - fired_time bigint not null, - sched_time bigint not null, - priority integer not null, - state varchar(16) not null, - job_name varchar(80), - job_group varchar(80), - is_nonconcurrent varchar(1), - requests_recovery varchar(1), - primary key (sched_name,entry_id) -); - - -create table qrtz_paused_trigger_grps( - sched_name varchar(120) not null, - trigger_group varchar(80) not null, - primary key (sched_name,trigger_group) -); - -create table qrtz_scheduler_state ( - sched_name varchar(120) not null, - instance_name varchar(80) not null, - last_checkin_time bigint not null, - checkin_interval bigint not null, - primary key (sched_name,instance_name) -); - -create table qrtz_locks - ( - sched_name varchar(120) not null, - lock_name varchar(40) not null, - primary key (sched_name,lock_name) -); diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_h2.sql b/spring-boot-demo-task-quartz/init/dbTables/tables_h2.sql deleted file mode 100644 index cc23d3c..0000000 --- a/spring-boot-demo-task-quartz/init/dbTables/tables_h2.sql +++ /dev/null @@ -1,249 +0,0 @@ --- Thanks to Amir Kibbar and Peter Rietzler for contributing the schema for H2 database, --- and verifying that it works with Quartz's StdJDBCDelegate --- --- Note, Quartz depends on row-level locking which means you must use the MVCC=TRUE --- setting on your H2 database, or you will experience dead-locks --- --- --- In your Quartz properties file, you'll need to set --- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate - -CREATE TABLE QRTZ_CALENDARS ( - SCHED_NAME VARCHAR(120) NOT NULL, - CALENDAR_NAME VARCHAR (200) NOT NULL , - CALENDAR IMAGE NOT NULL -); - -CREATE TABLE QRTZ_CRON_TRIGGERS ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR (200) NOT NULL , - TRIGGER_GROUP VARCHAR (200) NOT NULL , - CRON_EXPRESSION VARCHAR (120) NOT NULL , - TIME_ZONE_ID VARCHAR (80) -); - -CREATE TABLE QRTZ_FIRED_TRIGGERS ( - SCHED_NAME VARCHAR(120) NOT NULL, - ENTRY_ID VARCHAR (95) NOT NULL , - TRIGGER_NAME VARCHAR (200) NOT NULL , - TRIGGER_GROUP VARCHAR (200) NOT NULL , - INSTANCE_NAME VARCHAR (200) NOT NULL , - FIRED_TIME BIGINT NOT NULL , - SCHED_TIME BIGINT NOT NULL , - PRIORITY INTEGER NOT NULL , - STATE VARCHAR (16) NOT NULL, - JOB_NAME VARCHAR (200) NULL , - JOB_GROUP VARCHAR (200) NULL , - IS_NONCONCURRENT BOOLEAN NULL , - REQUESTS_RECOVERY BOOLEAN NULL -); - -CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_GROUP VARCHAR (200) NOT NULL -); - -CREATE TABLE QRTZ_SCHEDULER_STATE ( - SCHED_NAME VARCHAR(120) NOT NULL, - INSTANCE_NAME VARCHAR (200) NOT NULL , - LAST_CHECKIN_TIME BIGINT NOT NULL , - CHECKIN_INTERVAL BIGINT NOT NULL -); - -CREATE TABLE QRTZ_LOCKS ( - SCHED_NAME VARCHAR(120) NOT NULL, - LOCK_NAME VARCHAR (40) NOT NULL -); - -CREATE TABLE QRTZ_JOB_DETAILS ( - SCHED_NAME VARCHAR(120) NOT NULL, - JOB_NAME VARCHAR (200) NOT NULL , - JOB_GROUP VARCHAR (200) NOT NULL , - DESCRIPTION VARCHAR (250) NULL , - JOB_CLASS_NAME VARCHAR (250) NOT NULL , - IS_DURABLE BOOLEAN NOT NULL , - IS_NONCONCURRENT BOOLEAN NOT NULL , - IS_UPDATE_DATA BOOLEAN NOT NULL , - REQUESTS_RECOVERY BOOLEAN NOT NULL , - JOB_DATA IMAGE NULL -); - -CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR (200) NOT NULL , - TRIGGER_GROUP VARCHAR (200) NOT NULL , - REPEAT_COUNT BIGINT NOT NULL , - REPEAT_INTERVAL BIGINT NOT NULL , - TIMES_TRIGGERED BIGINT NOT NULL -); - -CREATE TABLE qrtz_simprop_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR(200) NOT NULL, - TRIGGER_GROUP VARCHAR(200) NOT NULL, - STR_PROP_1 VARCHAR(512) NULL, - STR_PROP_2 VARCHAR(512) NULL, - STR_PROP_3 VARCHAR(512) NULL, - INT_PROP_1 INTEGER NULL, - INT_PROP_2 INTEGER NULL, - LONG_PROP_1 BIGINT NULL, - LONG_PROP_2 BIGINT NULL, - DEC_PROP_1 NUMERIC(13,4) NULL, - DEC_PROP_2 NUMERIC(13,4) NULL, - BOOL_PROP_1 BOOLEAN NULL, - BOOL_PROP_2 BOOLEAN NULL, -); - -CREATE TABLE QRTZ_BLOB_TRIGGERS ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR (200) NOT NULL , - TRIGGER_GROUP VARCHAR (200) NOT NULL , - BLOB_DATA IMAGE NULL -); - -CREATE TABLE QRTZ_TRIGGERS ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR (200) NOT NULL , - TRIGGER_GROUP VARCHAR (200) NOT NULL , - JOB_NAME VARCHAR (200) NOT NULL , - JOB_GROUP VARCHAR (200) NOT NULL , - DESCRIPTION VARCHAR (250) NULL , - NEXT_FIRE_TIME BIGINT NULL , - PREV_FIRE_TIME BIGINT NULL , - PRIORITY INTEGER NULL , - TRIGGER_STATE VARCHAR (16) NOT NULL , - TRIGGER_TYPE VARCHAR (8) NOT NULL , - START_TIME BIGINT NOT NULL , - END_TIME BIGINT NULL , - CALENDAR_NAME VARCHAR (200) NULL , - MISFIRE_INSTR SMALLINT NULL , - JOB_DATA IMAGE NULL -); - -ALTER TABLE QRTZ_CALENDARS ADD - CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY - ( - SCHED_NAME, - CALENDAR_NAME - ); - -ALTER TABLE QRTZ_CRON_TRIGGERS ADD - CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ); - -ALTER TABLE QRTZ_FIRED_TRIGGERS ADD - CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY - ( - SCHED_NAME, - ENTRY_ID - ); - -ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD - CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY - ( - SCHED_NAME, - TRIGGER_GROUP - ); - -ALTER TABLE QRTZ_SCHEDULER_STATE ADD - CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY - ( - SCHED_NAME, - INSTANCE_NAME - ); - -ALTER TABLE QRTZ_LOCKS ADD - CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY - ( - SCHED_NAME, - LOCK_NAME - ); - -ALTER TABLE QRTZ_JOB_DETAILS ADD - CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY - ( - SCHED_NAME, - JOB_NAME, - JOB_GROUP - ); - -ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD - CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ); - -ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD - CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ); - -ALTER TABLE QRTZ_TRIGGERS ADD - CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ); - -ALTER TABLE QRTZ_CRON_TRIGGERS ADD - CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) REFERENCES QRTZ_TRIGGERS ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) ON DELETE CASCADE; - - -ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD - CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) REFERENCES QRTZ_TRIGGERS ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) ON DELETE CASCADE; - -ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD - CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY - ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) REFERENCES QRTZ_TRIGGERS ( - SCHED_NAME, - TRIGGER_NAME, - TRIGGER_GROUP - ) ON DELETE CASCADE; - - -ALTER TABLE QRTZ_TRIGGERS ADD - CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY - ( - SCHED_NAME, - JOB_NAME, - JOB_GROUP - ) REFERENCES QRTZ_JOB_DETAILS ( - SCHED_NAME, - JOB_NAME, - JOB_GROUP - ); - -COMMIT; diff --git a/spring-boot-demo-task-quartz/init/dbTables/tables_pointbase.sql b/spring-boot-demo-task-quartz/init/dbTables/tables_pointbase.sql deleted file mode 100644 index debc93f..0000000 --- a/spring-boot-demo-task-quartz/init/dbTables/tables_pointbase.sql +++ /dev/null @@ -1,180 +0,0 @@ -# -# Thanks to Gregg Freeman -# -# -# ...you may want to change defined the size of the "blob" columns before -# creating the tables (particularly for the qrtz_job_details.job_data column), -# if you will be storing large amounts of data in them -# -# -delete from qrtz_fired_triggers; -delete from qrtz_simple_triggers; -delete from qrtz_simprop_triggers; -delete from qrtz_cron_triggers; -delete from qrtz_blob_triggers; -delete from qrtz_triggers; -delete from qrtz_job_details; -delete from qrtz_calendars; -delete from qrtz_paused_trigger_grps; -delete from qrtz_locks; -delete from qrtz_scheduler_state; - -drop table qrtz_calendars; -drop table qrtz_fired_triggers; -drop table qrtz_blob_triggers; -drop table qrtz_cron_triggers; -drop table qrtz_simple_triggers; -drop table qrtz_simprop_triggers; -drop table qrtz_triggers; -drop table qrtz_job_details; -drop table qrtz_paused_trigger_grps; -drop table qrtz_locks; -drop table qrtz_scheduler_state; - - -CREATE TABLE qrtz_job_details - ( - SCHED_NAME VARCHAR(120) NOT NULL, - JOB_NAME VARCHAR2(80) NOT NULL, - JOB_GROUP VARCHAR2(80) NOT NULL, - DESCRIPTION VARCHAR2(120) NULL, - JOB_CLASS_NAME VARCHAR2(128) NOT NULL, - IS_DURABLE BOOLEAN NOT NULL, - IS_NONCONCURRENT BOOLEAN NOT NULL, - IS_UPDATE_DATA BOOLEAN NOT NULL, - REQUESTS_RECOVERY BOOLEAN NOT NULL, - JOB_DATA BLOB(4K) NULL, - PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) -); - -CREATE TABLE qrtz_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR2(80) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - JOB_NAME VARCHAR2(80) NOT NULL, - JOB_GROUP VARCHAR2(80) NOT NULL, - DESCRIPTION VARCHAR2(120) NULL, - NEXT_FIRE_TIME NUMBER(13) NULL, - PREV_FIRE_TIME NUMBER(13) NULL, - PRIORITY NUMBER(13) NULL, - TRIGGER_STATE VARCHAR2(16) NOT NULL, - TRIGGER_TYPE VARCHAR2(8) NOT NULL, - START_TIME NUMBER(13) NOT NULL, - END_TIME NUMBER(13) NULL, - CALENDAR_NAME VARCHAR2(80) NULL, - MISFIRE_INSTR NUMBER(2) NULL, - JOB_DATA BLOB(4K) NULL, - PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) - REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) -); - -CREATE TABLE qrtz_simple_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR2(80) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - REPEAT_COUNT NUMBER(7) NOT NULL, - REPEAT_INTERVAL NUMBER(12) NOT NULL, - TIMES_TRIGGERED NUMBER(10) NOT NULL, - PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) -); - - -CREATE TABLE qrtz_simprop_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR(200) NOT NULL, - TRIGGER_GROUP VARCHAR(200) NOT NULL, - STR_PROP_1 VARCHAR(512) NULL, - STR_PROP_2 VARCHAR(512) NULL, - STR_PROP_3 VARCHAR(512) NULL, - INT_PROP_1 NUMBER(10) NULL, - INT_PROP_2 NUMBER(10) NULL, - LONG_PROP_1 NUMBER(13) NULL, - LONG_PROP_2 NUMBER(13) NULL, - DEC_PROP_1 NUMERIC(13,4) NULL, - DEC_PROP_2 NUMERIC(13,4) NULL, - BOOL_PROP_1 BOOLEAN NULL, - BOOL_PROP_2 BOOLEAN NULL, - PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) -); - -CREATE TABLE qrtz_cron_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR2(80) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - CRON_EXPRESSION VARCHAR2(120) NOT NULL, - TIME_ZONE_ID VARCHAR2(80), - PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) -); - -CREATE TABLE qrtz_blob_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_NAME VARCHAR2(80) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - BLOB_DATA BLOB(4K) NULL, - PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), - FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) - REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) -); - -CREATE TABLE qrtz_calendars - ( - SCHED_NAME VARCHAR(120) NOT NULL, - CALENDAR_NAME VARCHAR2(80) NOT NULL, - CALENDAR BLOB(4K) NOT NULL, - PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) -); - -CREATE TABLE qrtz_paused_trigger_grps - ( - SCHED_NAME VARCHAR(120) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) -); - -CREATE TABLE qrtz_fired_triggers - ( - SCHED_NAME VARCHAR(120) NOT NULL, - ENTRY_ID VARCHAR2(95) NOT NULL, - TRIGGER_NAME VARCHAR2(80) NOT NULL, - TRIGGER_GROUP VARCHAR2(80) NOT NULL, - INSTANCE_NAME VARCHAR2(80) NOT NULL, - FIRED_TIME NUMBER(13) NOT NULL, - SCHED_TIME NUMBER(13) NOT NULL, - PRIORITY NUMBER(13) NOT NULL, - STATE VARCHAR2(16) NOT NULL, - JOB_NAME VARCHAR2(80) NULL, - JOB_GROUP VARCHAR2(80) NULL, - IS_NONCONCURRENT BOOLEAN NULL, - REQUESTS_RECOVERY BOOLEAN NULL, - PRIMARY KEY (SCHED_NAME,ENTRY_ID) -); - -CREATE TABLE qrtz_scheduler_state - ( - SCHED_NAME VARCHAR(120) NOT NULL, - INSTANCE_NAME VARCHAR2(80) NOT NULL, - LAST_CHECKIN_TIME NUMBER(13) NOT NULL, - CHECKIN_INTERVAL NUMBER(13) NOT NULL, - PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) -); - -CREATE TABLE qrtz_locks - ( - SCHED_NAME VARCHAR(120) NOT NULL, - LOCK_NAME VARCHAR2(40) NOT NULL, - PRIMARY KEY (SCHED_NAME,LOCK_NAME) -); - -commit; diff --git a/spring-boot-demo-task-quartz/pom.xml b/spring-boot-demo-task-quartz/pom.xml deleted file mode 100644 index 9fab559..0000000 --- a/spring-boot-demo-task-quartz/pom.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-task-quartz - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-task-quartz - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.0 - 1.2.10 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-quartz - - - - tk.mybatis - mapper-spring-boot-starter - ${mybatis.mapper.version} - - - - com.github.pagehelper - pagehelper-spring-boot-starter - ${mybatis.pagehelper.version} - - - - mysql - mysql-connector-java - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-task-quartz - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java deleted file mode 100644 index ba40b18..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/SpringBootDemoTaskQuartzApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.task.quartz; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import tk.mybatis.spring.annotation.MapperScan; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.task.quartz - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/23 20:33 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@MapperScan(basePackages = {"com.xkcoding.task.quartz.mapper"}) -@SpringBootApplication -public class SpringBootDemoTaskQuartzApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTaskQuartzApplication.class, args); - } -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java deleted file mode 100644 index 81dad5e..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/common/ApiResponse.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.xkcoding.task.quartz.common; - -import lombok.Data; -import org.springframework.http.HttpStatus; - -import java.io.Serializable; - -/** - *

    - * 通用Api封装 - *

    - * - * @package: com.xkcoding.task.quartz.common - * @description: 通用Api封装 - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:59 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ApiResponse implements Serializable { - /** - * 返回信息 - */ - private String message; - - /** - * 返回数据 - */ - private Object data; - - public ApiResponse() { - } - - private ApiResponse(String message, Object data) { - this.message = message; - this.data = data; - } - - /** - * 通用封装获取ApiResponse对象 - * - * @param message 返回信息 - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse of(String message, Object data) { - return new ApiResponse(message, data); - } - - /** - * 通用成功封装获取ApiResponse对象 - * - * @param data 返回数据 - * @return ApiResponse - */ - public static ApiResponse ok(Object data) { - return new ApiResponse(HttpStatus.OK.getReasonPhrase(), data); - } - - /** - * 通用封装获取ApiResponse对象 - * - * @param message 返回信息 - * @return ApiResponse - */ - public static ApiResponse msg(String message) { - return of(message, null); - } - -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java deleted file mode 100644 index 2e167ec..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/controller/JobController.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.xkcoding.task.quartz.controller; - -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.github.pagehelper.PageInfo; -import com.xkcoding.task.quartz.common.ApiResponse; -import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; -import com.xkcoding.task.quartz.entity.form.JobForm; -import com.xkcoding.task.quartz.service.JobService; -import lombok.extern.slf4j.Slf4j; -import org.quartz.SchedulerException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; - -/** - *

    - * Job Controller - *

    - * - * @package: com.xkcoding.task.quartz.controller - * @description: Job Controller - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:23 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/job") -@Slf4j -public class JobController { - private final JobService jobService; - - @Autowired - public JobController(JobService jobService) { - this.jobService = jobService; - } - - /** - * 保存定时任务 - */ - @PostMapping - public ResponseEntity addJob(@Valid JobForm form) { - try { - jobService.addJob(form); - } catch (Exception e) { - return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); - } - - return new ResponseEntity<>(ApiResponse.msg("操作成功"), HttpStatus.CREATED); - } - - /** - * 删除定时任务 - */ - @DeleteMapping - public ResponseEntity deleteJob(JobForm form) throws SchedulerException { - if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { - return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); - } - - jobService.deleteJob(form); - return new ResponseEntity<>(ApiResponse.msg("删除成功"), HttpStatus.OK); - } - - /** - * 暂停定时任务 - */ - @PutMapping(params = "pause") - public ResponseEntity pauseJob(JobForm form) throws SchedulerException { - if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { - return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); - } - - jobService.pauseJob(form); - return new ResponseEntity<>(ApiResponse.msg("暂停成功"), HttpStatus.OK); - } - - /** - * 恢复定时任务 - */ - @PutMapping(params = "resume") - public ResponseEntity resumeJob(JobForm form) throws SchedulerException { - if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) { - return new ResponseEntity<>(ApiResponse.msg("参数不能为空"), HttpStatus.BAD_REQUEST); - } - - jobService.resumeJob(form); - return new ResponseEntity<>(ApiResponse.msg("恢复成功"), HttpStatus.OK); - } - - /** - * 修改定时任务,定时时间 - */ - @PutMapping(params = "cron") - public ResponseEntity cronJob(@Valid JobForm form) { - try { - jobService.cronJob(form); - } catch (Exception e) { - return new ResponseEntity<>(ApiResponse.msg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); - } - - return new ResponseEntity<>(ApiResponse.msg("修改成功"), HttpStatus.OK); - } - - @GetMapping - public ResponseEntity jobList(Integer currentPage, Integer pageSize) { - if (ObjectUtil.isNull(currentPage)) { - currentPage = 1; - } - if (ObjectUtil.isNull(pageSize)) { - pageSize = 10; - } - PageInfo all = jobService.list(currentPage, pageSize); - return ResponseEntity.ok(ApiResponse.ok(Dict.create().set("total", all.getTotal()).set("data", all.getList()))); - } - -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java deleted file mode 100644 index 5a05b92..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/domain/JobAndTrigger.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.xkcoding.task.quartz.entity.domain; - -import lombok.Data; - -import java.math.BigInteger; - -/** - *

    - * 实体类 - *

    - * - * @package: com.xkcoding.task.quartz.entity.domain - * @description: 实体类 - * @author: yangkai.shen - * @date: Created in 2018-11-26 15:05 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class JobAndTrigger { - /** - * 定时任务名称 - */ - private String jobName; - /** - * 定时任务组 - */ - private String jobGroup; - /** - * 定时任务全类名 - */ - private String jobClassName; - /** - * 触发器名称 - */ - private String triggerName; - /** - * 触发器组 - */ - private String triggerGroup; - /** - * 重复间隔 - */ - private BigInteger repeatInterval; - /** - * 触发次数 - */ - private BigInteger timesTriggered; - /** - * cron 表达式 - */ - private String cronExpression; - /** - * 时区 - */ - private String timeZoneId; - /** - * 定时任务状态 - */ - private String triggerState; -} \ No newline at end of file diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java deleted file mode 100644 index d571083..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/entity/form/JobForm.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.task.quartz.entity.form; - -import lombok.Data; -import lombok.experimental.Accessors; - -import javax.validation.constraints.NotBlank; - -/** - *

    - * 定时任务详情 - *

    - * - * @package: com.xkcoding.task.quartz.entity.form - * @description: 定时任务详情 - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@Accessors(chain = true) -public class JobForm { - /** - * 定时任务全类名 - */ - @NotBlank(message = "类名不能为空") - private String jobClassName; - /** - * 任务组名 - */ - @NotBlank(message = "任务组名不能为空") - private String jobGroupName; - /** - * 定时任务cron表达式 - */ - @NotBlank(message = "cron表达式不能为空") - private String cronExpression; -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java deleted file mode 100644 index f56899c..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/HelloJob.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xkcoding.task.quartz.job; - -import cn.hutool.core.date.DateUtil; -import com.xkcoding.task.quartz.job.base.BaseJob; -import lombok.extern.slf4j.Slf4j; -import org.quartz.JobExecutionContext; - -/** - *

    - * Hello Job - *

    - * - * @package: com.xkcoding.task.quartz.job - * @description: Hello Job - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class HelloJob implements BaseJob { - - @Override - public void execute(JobExecutionContext context) { - log.error("Hello Job 执行时间: {}", DateUtil.now()); - } -} \ No newline at end of file diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java deleted file mode 100644 index a42d184..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/TestJob.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xkcoding.task.quartz.job; - -import cn.hutool.core.date.DateUtil; -import com.xkcoding.task.quartz.job.base.BaseJob; -import lombok.extern.slf4j.Slf4j; -import org.quartz.JobExecutionContext; - -/** - *

    - * Test Job - *

    - * - * @package: com.xkcoding.task.quartz.job - * @description: Test Job - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -public class TestJob implements BaseJob { - - @Override - public void execute(JobExecutionContext context) { - log.error("Test Job 执行时间: {}", DateUtil.now()); - } -} \ No newline at end of file diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java deleted file mode 100644 index cc5156f..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/job/base/BaseJob.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.task.quartz.job.base; - -import org.quartz.*; - -/** - *

    - * Job 基类,主要是在 {@link org.quartz.Job} 上再封装一层,只让我们自己项目里的Job去实现 - *

    - * - * @package: com.xkcoding.task.quartz.job.base - * @description: Job 基类,主要是在 {@link org.quartz.Job} 上再封装一层,只让我们自己项目里的Job去实现 - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:27 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface BaseJob extends Job { - /** - *

    - * Called by the {@link Scheduler} when a {@link Trigger} - * fires that is associated with the Job. - *

    - * - *

    - * The implementation may wish to set a - * {@link JobExecutionContext#setResult(Object) result} object on the - * {@link JobExecutionContext} before this method exits. The result itself - * is meaningless to Quartz, but may be informative to - * {@link JobListener}s or - * {@link TriggerListener}s that are watching the job's - * execution. - *

    - * - * @param context 上下文 - * @throws JobExecutionException if there is an exception while executing the job. - */ - @Override - void execute(JobExecutionContext context) throws JobExecutionException; -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java deleted file mode 100644 index f59885b..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/mapper/JobMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.task.quartz.mapper; - -import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - *

    - * Job Mapper - *

    - * - * @package: com.xkcoding.task.quartz.mapper - * @description: Job Mapper - * @author: yangkai.shen - * @date: Created in 2018-11-26 15:12 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public interface JobMapper { - /** - * 查询定时作业和触发器列表 - * - * @return 定时作业和触发器列表 - */ - List list(); -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java deleted file mode 100644 index d8cb57b..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/JobService.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.xkcoding.task.quartz.service; - -import com.github.pagehelper.PageInfo; -import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; -import com.xkcoding.task.quartz.entity.form.JobForm; -import org.quartz.JobDetail; -import org.quartz.SchedulerException; - -/** - *

    - * Job Service - *

    - * - * @package: com.xkcoding.task.quartz.service - * @description: Job Service - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface JobService { - /** - * 添加并启动定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws Exception 异常 - */ - void addJob(JobForm form) throws Exception; - - /** - * 删除定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - void deleteJob(JobForm form) throws SchedulerException; - - /** - * 暂停定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - void pauseJob(JobForm form) throws SchedulerException; - - /** - * 恢复定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - void resumeJob(JobForm form) throws SchedulerException; - - /** - * 重新配置定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws Exception 异常 - */ - void cronJob(JobForm form) throws Exception; - - /** - * 查询定时任务列表 - * - * @param currentPage 当前页 - * @param pageSize 每页条数 - * @return 定时任务列表 - */ - PageInfo list(Integer currentPage, Integer pageSize); -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java deleted file mode 100644 index 63eb61f..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/service/impl/JobServiceImpl.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.xkcoding.task.quartz.service.impl; - -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; -import com.xkcoding.task.quartz.entity.domain.JobAndTrigger; -import com.xkcoding.task.quartz.entity.form.JobForm; -import com.xkcoding.task.quartz.mapper.JobMapper; -import com.xkcoding.task.quartz.service.JobService; -import com.xkcoding.task.quartz.util.JobUtil; -import lombok.extern.slf4j.Slf4j; -import org.quartz.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - *

    - * Job Service - *

    - * - * @package: com.xkcoding.task.quartz.service.impl - * @description: Job Service - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class JobServiceImpl implements JobService { - private final Scheduler scheduler; - private final JobMapper jobMapper; - - @Autowired - public JobServiceImpl(Scheduler scheduler, JobMapper jobMapper) { - this.scheduler = scheduler; - this.jobMapper = jobMapper; - } - - /** - * 添加并启动定时任务 - * - * @param form 表单参数 {@link JobForm} - * @return {@link JobDetail} - * @throws Exception 异常 - */ - @Override - public void addJob(JobForm form) throws Exception { - // 启动调度器 - scheduler.start(); - - // 构建Job信息 - JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobClassName(), form.getJobGroupName()).build(); - - // Cron表达式调度构建器(即任务执行的时间) - CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getCronExpression()); - - //根据Cron表达式构建一个Trigger - CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobClassName(), form.getJobGroupName()).withSchedule(cron).build(); - - try { - scheduler.scheduleJob(jobDetail, trigger); - } catch (SchedulerException e) { - log.error("【定时任务】创建失败!", e); - throw new Exception("【定时任务】创建失败!"); - } - - } - - /** - * 删除定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - @Override - public void deleteJob(JobForm form) throws SchedulerException { - scheduler.pauseTrigger(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName())); - scheduler.unscheduleJob(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName())); - scheduler.deleteJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); - } - - /** - * 暂停定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - @Override - public void pauseJob(JobForm form) throws SchedulerException { - scheduler.pauseJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); - } - - /** - * 恢复定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws SchedulerException 异常 - */ - @Override - public void resumeJob(JobForm form) throws SchedulerException { - scheduler.resumeJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName())); - } - - /** - * 重新配置定时任务 - * - * @param form 表单参数 {@link JobForm} - * @throws Exception 异常 - */ - @Override - public void cronJob(JobForm form) throws Exception { - try { - TriggerKey triggerKey = TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()); - // 表达式调度构建器 - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(form.getCronExpression()); - - CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); - - // 根据Cron表达式构建一个Trigger - trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); - - // 按新的trigger重新设置job执行 - scheduler.rescheduleJob(triggerKey, trigger); - } catch (SchedulerException e) { - log.error("【定时任务】更新失败!", e); - throw new Exception("【定时任务】创建失败!"); - } - } - - /** - * 查询定时任务列表 - * - * @param currentPage 当前页 - * @param pageSize 每页条数 - * @return 定时任务列表 - */ - @Override - public PageInfo list(Integer currentPage, Integer pageSize) { - PageHelper.startPage(currentPage, pageSize); - List list = jobMapper.list(); - return new PageInfo<>(list); - } -} diff --git a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java b/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java deleted file mode 100644 index 49c0603..0000000 --- a/spring-boot-demo-task-quartz/src/main/java/com/xkcoding/task/quartz/util/JobUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.task.quartz.util; - -import com.xkcoding.task.quartz.job.base.BaseJob; - -/** - *

    - * 定时任务反射工具类 - *

    - * - * @package: com.xkcoding.task.quartz.util - * @description: 定时任务反射工具类 - * @author: yangkai.shen - * @date: Created in 2018-11-26 13:33 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class JobUtil { - /** - * 根据全类名获取Job实例 - * - * @param classname Job全类名 - * @return {@link BaseJob} 实例 - * @throws Exception 泛型获取异常 - */ - public static BaseJob getClass(String classname) throws Exception { - Class clazz = Class.forName(classname); - return (BaseJob) clazz.newInstance(); - } -} diff --git a/spring-boot-demo-task-xxl-job/pom.xml b/spring-boot-demo-task-xxl-job/pom.xml deleted file mode 100644 index efa9656..0000000 --- a/spring-boot-demo-task-xxl-job/pom.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-task-xxl-job - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-task-xxl-job - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 2.1.0 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - com.xuxueli - xxl-job-core - ${xxl-job.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-task-xxl-job - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-task/README.md b/spring-boot-demo-task/README.md deleted file mode 100644 index 280c8d0..0000000 --- a/spring-boot-demo-task/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# spring-boot-demo-task - -> 此 demo 主要演示了 Spring Boot 如何快速实现定时任务。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-task - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-task - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.apache.commons - commons-lang3 - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-task - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## TaskConfig.java - -> 此处等同于在配置文件配置 -> -> ```properties -> spring.task.scheduling.pool.size=20 -> spring.task.scheduling.thread-name-prefix=Job-Thread- -> ``` - -```java -/** - *

    - * 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 - *

    - * - * @package: com.xkcoding.task.config - * @description: 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 - * @author: yangkai.shen - * @date: Created in 2018/11/22 19:02 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableScheduling -@ComponentScan(basePackages = {"com.xkcoding.task.job"}) -public class TaskConfig implements SchedulingConfigurer { - @Override - public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(taskExecutor()); - } - - /** - * 这里等同于配置文件配置 - * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. - * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. - * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} - */ - @Bean - public Executor taskExecutor() { - return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); - } -} -``` - -## TaskJob.java - -```java -/** - *

    - * 定时任务 - *

    - * - * @package: com.xkcoding.task.job - * @description: 定时任务 - * @author: yangkai.shen - * @date: Created in 2018/11/22 19:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class TaskJob { - - /** - * 按照标准时间来算,每隔 10s 执行一次 - */ - @Scheduled(cron = "0/10 * * * * ?") - public void job1() { - log.info("【job1】开始执行:{}", DateUtil.formatDateTime(new Date())); - } - - /** - * 从启动时间开始,间隔 2s 执行 - * 固定间隔时间 - */ - @Scheduled(fixedRate = 2000) - public void job2() { - log.info("【job2】开始执行:{}", DateUtil.formatDateTime(new Date())); - } - - /** - * 从启动时间开始,延迟 5s 后间隔 4s 执行 - * 固定等待时间 - */ - @Scheduled(fixedDelay = 4000, initialDelay = 5000) - public void job3() { - log.info("【job3】开始执行:{}", DateUtil.formatDateTime(new Date())); - } -} -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -# 下面的配置等同于 TaskConfig -#spring: -# task: -# scheduling: -# pool: -# size: 20 -# thread-name-prefix: Job-Thread- -``` - -## 参考 - -- Spring Boot官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-task-execution-scheduling \ No newline at end of file diff --git a/spring-boot-demo-task/pom.xml b/spring-boot-demo-task/pom.xml deleted file mode 100644 index 9405fc6..0000000 --- a/spring-boot-demo-task/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-task - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-task - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.apache.commons - commons-lang3 - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-task - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java b/spring-boot-demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java deleted file mode 100644 index 55687ad..0000000 --- a/spring-boot-demo-task/src/main/java/com/xkcoding/task/SpringBootDemoTaskApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.task; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.task - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018/11/22 19:00 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTaskApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTaskApplication.class, args); - } -} diff --git a/spring-boot-demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java b/spring-boot-demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java deleted file mode 100644 index 43ec481..0000000 --- a/spring-boot-demo-task/src/main/java/com/xkcoding/task/config/TaskConfig.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.xkcoding.task.config; - -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.SchedulingConfigurer; -import org.springframework.scheduling.config.ScheduledTaskRegistrar; - -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledThreadPoolExecutor; - -/** - *

    - * 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 - *

    - * - * @package: com.xkcoding.task.config - * @description: 定时任务配置,配置线程池,使用不同线程执行任务,提升效率 - * @author: yangkai.shen - * @date: Created in 2018/11/22 19:02 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableScheduling -@ComponentScan(basePackages = {"com.xkcoding.task.job"}) -public class TaskConfig implements SchedulingConfigurer { - @Override - public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(taskExecutor()); - } - - /** - * 这里等同于配置文件配置 - * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. - * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. - * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} - */ - @Bean - public Executor taskExecutor() { - return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); - } -} diff --git a/spring-boot-demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java b/spring-boot-demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java deleted file mode 100644 index dfbc912..0000000 --- a/spring-boot-demo-task/src/main/java/com/xkcoding/task/job/TaskJob.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.xkcoding.task.job; - -import cn.hutool.core.date.DateUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.Date; - -/** - *

    - * 定时任务 - *

    - * - * @package: com.xkcoding.task.job - * @description: 定时任务 - * @author: yangkai.shen - * @date: Created in 2018/11/22 19:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class TaskJob { - - /** - * 按照标准时间来算,每隔 10s 执行一次 - */ - @Scheduled(cron = "0/10 * * * * ?") - public void job1() { - log.info("【job1】开始执行:{}", DateUtil.formatDateTime(new Date())); - } - - /** - * 从启动时间开始,间隔 2s 执行 - * 固定间隔时间 - */ - @Scheduled(fixedRate = 2000) - public void job2() { - log.info("【job2】开始执行:{}", DateUtil.formatDateTime(new Date())); - } - - /** - * 从启动时间开始,延迟 5s 后间隔 4s 执行 - * 固定等待时间 - */ - @Scheduled(fixedDelay = 4000, initialDelay = 5000) - public void job3() { - log.info("【job3】开始执行:{}", DateUtil.formatDateTime(new Date())); - } -} \ No newline at end of file diff --git a/spring-boot-demo-template-beetl/README.md b/spring-boot-demo-template-beetl/README.md deleted file mode 100644 index f68e92f..0000000 --- a/spring-boot-demo-template-beetl/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# spring-boot-demo-template-beetl - -> 本 demo 主要演示了 Spring Boot 项目如何集成 beetl 模板引擎 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-template-beetl - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-beetl - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.63.RELEASE - - - - - com.ibeetl - beetl-framework-starter - ${ibeetl.version} - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-beetl - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## IndexController.java - -```java -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.beetl.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:17 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index.btl"); - mv.addObject(user); - } - - return mv; - } -} -``` - -## UserController.java - -```java -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.beetl.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:17 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login.btl"); - } -} -``` - -## index.html - -```jsp - - -<% include("/common/head.html"){} %> - -
    - 欢迎登录,${user.name}! -
    - - -``` - -## login.html - -```jsp - - -<% include("/common/head.html"){} %> - -
    -
    - 用户名 - 密码 - -
    -
    - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -``` - -## Beetl 语法糖学习文档 - -http://ibeetl.com/guide/#beetl - diff --git a/spring-boot-demo-template-beetl/pom.xml b/spring-boot-demo-template-beetl/pom.xml deleted file mode 100644 index 39324c0..0000000 --- a/spring-boot-demo-template-beetl/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-template-beetl - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-beetl - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.1.63.RELEASE - - - - - com.ibeetl - beetl-framework-starter - ${ibeetl.version} - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-beetl - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java b/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java deleted file mode 100644 index ccd59fc..0000000 --- a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.template.beetl; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.template.beetl - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:17 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTemplateBeetlApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTemplateBeetlApplication.class, args); - } -} diff --git a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java b/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java deleted file mode 100644 index 6c05f41..0000000 --- a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/IndexController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.template.beetl.controller; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.template.beetl.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.beetl.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:17 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index.btl"); - mv.addObject(user); - } - - return mv; - } -} diff --git a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java b/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java deleted file mode 100644 index 8bafff5..0000000 --- a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/controller/UserController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.template.beetl.controller; - -import com.xkcoding.template.beetl.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.beetl.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:17 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login.btl"); - } -} diff --git a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java b/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java deleted file mode 100644 index 00854b9..0000000 --- a/spring-boot-demo-template-beetl/src/main/java/com/xkcoding/template/beetl/model/User.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.template.beetl.model; - -import lombok.Data; - -/** - *

    - * 用户 model - *

    - * - * @package: com.xkcoding.template.beetl.model - * @description: 用户 model - * @author: yangkai.shen - * @date: Created in 2018/10/10 11:18 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class User { - private String name; - private String password; -} diff --git a/spring-boot-demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java b/spring-boot-demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java deleted file mode 100644 index fa2a35f..0000000 --- a/spring-boot-demo-template-beetl/src/test/java/com/xkcoding/template/beetl/SpringBootDemoTemplateBeetlApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.template.beetl; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoTemplateBeetlApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-template-enjoy/README.md b/spring-boot-demo-template-enjoy/README.md deleted file mode 100644 index 4994129..0000000 --- a/spring-boot-demo-template-enjoy/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# spring-boot-demo-template-enjoy - -> 本 demo 主要演示了 Spring Boot 项目如何集成 enjoy 模板引擎。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-template-enjoy - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-enjoy - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.5 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.jfinal - enjoy - ${enjoy.version} - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-enjoy - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## EnjoyConfig.java - -```java -/** - *

    - * Enjoy 模板配置类 - *

    - * - * @package: com.xkcoding.template.enjoy.config - * @description: Enjoy 模板配置类 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:06 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class EnjoyConfig { - @Bean(name = "jfinalViewResolver") - public JFinalViewResolver getJFinalViewResolver() { - JFinalViewResolver jfr = new JFinalViewResolver(); - // setDevMode 配置放在最前面 - jfr.setDevMode(true); - // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件 - jfr.setSourceFactory(new ClassPathSourceFactory()); - // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath - // 代替 jfr.setPrefix("/view/") - JFinalViewResolver.engine.setBaseTemplatePath("/templates/"); - - jfr.setSessionInView(true); - jfr.setSuffix(".html"); - jfr.setContentType("text/html;charset=UTF-8"); - jfr.setOrder(0); - return jfr; - } -} -``` - -## IndexController.java - -```java -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.enjoy.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:22 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index"); - mv.addObject(user); - } - - return mv; - } -} -``` - -## UserController.java - -```java -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.enjoy.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:24 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login"); - } -} -``` - -## index.html - -```jsp - - -#include("/common/head.html") - -
    - 欢迎登录,#(user.name)! -
    - - -``` - -## login.html - -```jsp - - -#include("/common/head.html") - -
    -
    - 用户名 - 密码 - -
    -
    - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -``` - -## Enjoy 语法糖学习文档 - -http://www.jfinal.com/doc/6-1 - - - diff --git a/spring-boot-demo-template-enjoy/pom.xml b/spring-boot-demo-template-enjoy/pom.xml deleted file mode 100644 index 737160e..0000000 --- a/spring-boot-demo-template-enjoy/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-template-enjoy - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-enjoy - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.5 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.jfinal - enjoy - ${enjoy.version} - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-enjoy - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java b/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java deleted file mode 100644 index 46314b9..0000000 --- a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.template.enjoy; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.template.enjoy - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:06 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTemplateEnjoyApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTemplateEnjoyApplication.class, args); - } -} diff --git a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java b/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java deleted file mode 100644 index 246a557..0000000 --- a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/config/EnjoyConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.template.enjoy.config; - -import com.jfinal.template.ext.spring.JFinalViewResolver; -import com.jfinal.template.source.ClassPathSourceFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * Enjoy 模板配置类 - *

    - * - * @package: com.xkcoding.template.enjoy.config - * @description: Enjoy 模板配置类 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:06 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -public class EnjoyConfig { - @Bean(name = "jfinalViewResolver") - public JFinalViewResolver getJFinalViewResolver() { - JFinalViewResolver jfr = new JFinalViewResolver(); - // setDevMode 配置放在最前面 - jfr.setDevMode(true); - // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件 - jfr.setSourceFactory(new ClassPathSourceFactory()); - // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath - // 代替 jfr.setPrefix("/view/") - JFinalViewResolver.engine.setBaseTemplatePath("/templates/"); - - jfr.setSessionInView(true); - jfr.setSuffix(".html"); - jfr.setContentType("text/html;charset=UTF-8"); - jfr.setOrder(0); - return jfr; - } -} diff --git a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java b/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java deleted file mode 100644 index 6cc978d..0000000 --- a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/IndexController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.template.enjoy.controller; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.template.enjoy.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.enjoy.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:22 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index"); - mv.addObject(user); - } - - return mv; - } -} diff --git a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java b/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java deleted file mode 100644 index 41bddff..0000000 --- a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/controller/UserController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.template.enjoy.controller; - -import com.xkcoding.template.enjoy.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.enjoy.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:24 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login"); - } -} diff --git a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java b/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java deleted file mode 100644 index 99a7bce..0000000 --- a/spring-boot-demo-template-enjoy/src/main/java/com/xkcoding/template/enjoy/model/User.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.template.enjoy.model; - -import lombok.Data; - -/** - *

    - * 用户 model - *

    - * - * @package: com.xkcoding.template.enjoy.model - * @description: 用户 model - * @author: yangkai.shen - * @date: Created in 2018/10/11 2:21 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class User { - private String name; - private String password; -} diff --git a/spring-boot-demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java b/spring-boot-demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java deleted file mode 100644 index 476c25d..0000000 --- a/spring-boot-demo-template-enjoy/src/test/java/com/xkcoding/template/enjoy/SpringBootDemoTemplateEnjoyApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.template.enjoy; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoTemplateEnjoyApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-template-freemarker/README.md b/spring-boot-demo-template-freemarker/README.md deleted file mode 100644 index 825ab54..0000000 --- a/spring-boot-demo-template-freemarker/README.md +++ /dev/null @@ -1,198 +0,0 @@ -# spring-boot-demo-template-freemarker - -> 本 demo 主要演示了 Spring Boot 项目如何集成 freemarker 模板引擎 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-template-freemarker - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-freemarker - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-freemarker - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-freemarker - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## IndexController.java - -```java -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.freemarker.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:07 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("index"); - mv.addObject(user); - } - - return mv; - } -} -``` - -## UserController.java - -```java -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.freemarker.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:11 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("login"); - } -} -``` - -## index.ftl - -```jsp - - -<#include "./common/head.ftl"> - -
    - 欢迎登录,${user.name}! -
    - - -``` - -## login.ftl - -```jsp - - -<#include "./common/head.ftl"> - -
    -
    - 用户名 - 密码 - -
    -
    - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - freemarker: - suffix: .ftl - cache: false - charset: UTF-8 -``` - -## Freemarker 语法糖学习文档 - -https://freemarker.apache.org/docs/dgui.html - diff --git a/spring-boot-demo-template-freemarker/pom.xml b/spring-boot-demo-template-freemarker/pom.xml deleted file mode 100644 index f72fb11..0000000 --- a/spring-boot-demo-template-freemarker/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-template-freemarker - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-freemarker - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-freemarker - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-freemarker - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java b/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java deleted file mode 100644 index bd01c29..0000000 --- a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.template.freemarker; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.template.freemarker - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:17 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTemplateFreemarkerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTemplateFreemarkerApplication.class, args); - } -} diff --git a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java b/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java deleted file mode 100644 index d06aa4e..0000000 --- a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/IndexController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.template.freemarker.controller; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.template.freemarker.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.freemarker.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:07 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index"); - mv.addObject(user); - } - - return mv; - } -} diff --git a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java b/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java deleted file mode 100644 index 0631e06..0000000 --- a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/controller/UserController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.template.freemarker.controller; - -import com.xkcoding.template.freemarker.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.freemarker.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:11 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login"); - } -} diff --git a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java b/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java deleted file mode 100644 index d03e420..0000000 --- a/spring-boot-demo-template-freemarker/src/main/java/com/xkcoding/template/freemarker/model/User.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.template.freemarker.model; - -import lombok.Data; - -/** - *

    - * 用户 model - *

    - * - * @package: com.xkcoding.template.freemarker.model - * @description: 用户 model - * @author: yangkai.shen - * @date: Created in 2018/10/9 3:06 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class User { - private String name; - private String password; -} diff --git a/spring-boot-demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java b/spring-boot-demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java deleted file mode 100644 index 5f21f9b..0000000 --- a/spring-boot-demo-template-freemarker/src/test/java/com/xkcoding/template/freemarker/SpringBootDemoTemplateFreemarkerApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.template.freemarker; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoTemplateFreemarkerApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-template-thymeleaf/README.md b/spring-boot-demo-template-thymeleaf/README.md deleted file mode 100644 index 10569a3..0000000 --- a/spring-boot-demo-template-thymeleaf/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# spring-boot-demo-template-thymeleaf - -> 本 demo 主要演示了 Spring Boot 项目如何集成 thymeleaf 模板引擎 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-template-thymeleaf - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-thymeleaf - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-thymeleaf - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## IndexController.java - -```java -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.thymeleaf.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:12 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index"); - mv.addObject(user); - } - - return mv; - } -} -``` - -## UserController.java - -```java -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.thymeleaf.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:11 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login"); - } -} -``` - -## index.html - -```jsp - - -
    - -
    - 欢迎登录,! -
    - - -``` - -## login.html - -```jsp - - -
    - -
    -
    - 用户名 - 密码 - -
    -
    - - -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo -spring: - thymeleaf: - mode: HTML - encoding: UTF-8 - servlet: - content-type: text/html - cache: false -``` - -## Thymeleaf语法糖学习文档 - -https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html - diff --git a/spring-boot-demo-template-thymeleaf/pom.xml b/spring-boot-demo-template-thymeleaf/pom.xml deleted file mode 100644 index 835611c..0000000 --- a/spring-boot-demo-template-thymeleaf/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-template-thymeleaf - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-template-thymeleaf - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - cn.hutool - hutool-all - - - - - spring-boot-demo-template-thymeleaf - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java b/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java deleted file mode 100644 index 0bb1b92..0000000 --- a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.template.thymeleaf; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.template.thymeleaf - * @description: 启动类 - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:10 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTemplateThymeleafApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTemplateThymeleafApplication.class, args); - } -} diff --git a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java b/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java deleted file mode 100644 index 228554a..0000000 --- a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/IndexController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.template.thymeleaf.controller; - -import cn.hutool.core.util.ObjectUtil; -import com.xkcoding.template.thymeleaf.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 主页 - *

    - * - * @package: com.xkcoding.template.thymeleaf.controller - * @description: 主页 - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:12 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@Slf4j -public class IndexController { - - @GetMapping(value = {"", "/"}) - public ModelAndView index(HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - User user = (User) request.getSession().getAttribute("user"); - if (ObjectUtil.isNull(user)) { - mv.setViewName("redirect:/user/login"); - } else { - mv.setViewName("page/index"); - mv.addObject(user); - } - - return mv; - } -} diff --git a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java b/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java deleted file mode 100644 index 2b57459..0000000 --- a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/controller/UserController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xkcoding.template.thymeleaf.controller; - -import com.xkcoding.template.thymeleaf.model.User; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; - -/** - *

    - * 用户页面 - *

    - * - * @package: com.xkcoding.template.thymeleaf.controller - * @description: 用户页面 - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:11 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Controller -@RequestMapping("/user") -@Slf4j -public class UserController { - @PostMapping("/login") - public ModelAndView login(User user, HttpServletRequest request) { - ModelAndView mv = new ModelAndView(); - - mv.addObject(user); - mv.setViewName("redirect:/"); - - request.getSession().setAttribute("user", user); - return mv; - } - - @GetMapping("/login") - public ModelAndView login() { - return new ModelAndView("page/login"); - } -} diff --git a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java b/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java deleted file mode 100644 index 01592d4..0000000 --- a/spring-boot-demo-template-thymeleaf/src/main/java/com/xkcoding/template/thymeleaf/model/User.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.xkcoding.template.thymeleaf.model; - -import lombok.Data; - -/** - *

    - * 用户 model - *

    - * - * @package: com.xkcoding.template.thymeleaf.model - * @description: 用户 model - * @author: yangkai.shen - * @date: Created in 2018/10/10 10:11 AM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class User { - private String name; - private String password; -} diff --git a/spring-boot-demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java b/spring-boot-demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java deleted file mode 100644 index 734e6ac..0000000 --- a/spring-boot-demo-template-thymeleaf/src/test/java/com/xkcoding/template/thymeleaf/SpringBootDemoTemplateThymeleafApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.template.thymeleaf; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoTemplateThymeleafApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-tio/pom.xml b/spring-boot-demo-tio/pom.xml deleted file mode 100644 index b5f4a9e..0000000 --- a/spring-boot-demo-tio/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - spring-boot-demo-tio - 1.0.0-SNAPSHOT - spring-boot-demo-tio - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java b/spring-boot-demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java deleted file mode 100644 index 3d8746a..0000000 --- a/spring-boot-demo-tio/src/main/java/com/xkcoding/springbootdemotio/SpringBootDemoTioApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.springbootdemotio; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.springbootdemotio - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-02-05 18:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoTioApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoTioApplication.class, args); - } - -} - diff --git a/spring-boot-demo-uflo/pom.xml b/spring-boot-demo-uflo/pom.xml deleted file mode 100644 index e4ea002..0000000 --- a/spring-boot-demo-uflo/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-uflo - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-uflo - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-uflo - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-upload/README.md b/spring-boot-demo-upload/README.md deleted file mode 100644 index a139374..0000000 --- a/spring-boot-demo-upload/README.md +++ /dev/null @@ -1,519 +0,0 @@ -# spring-boot-demo-upload - -> 本 demo 演示了 Spring Boot 如何实现本地文件上传以及如何上传文件至七牛云平台。前端使用 vue 和 iview 实现上传页面。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-upload - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-upload - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.qiniu - qiniu-java-sdk - [7.2.0, 7.2.99] - - - - - spring-boot-demo-upload - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## UploadConfig.java - -```java -/** - *

    - * 上传配置 - *

    - * - * @package: com.xkcoding.upload.config - * @description: 上传配置 - * @author: yangkai.shen - * @date: Created in 2018/10/23 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class}) -@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) -@EnableConfigurationProperties(MultipartProperties.class) -public class UploadConfig { - @Value("${qiniu.accessKey}") - private String accessKey; - - @Value("${qiniu.secretKey}") - private String secretKey; - - private final MultipartProperties multipartProperties; - - @Autowired - public UploadConfig(MultipartProperties multipartProperties) { - this.multipartProperties = multipartProperties; - } - - /** - * 上传配置 - */ - @Bean - @ConditionalOnMissingBean - public MultipartConfigElement multipartConfigElement() { - return this.multipartProperties.createMultipartConfig(); - } - - /** - * 注册解析器 - */ - @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) - @ConditionalOnMissingBean(MultipartResolver.class) - public StandardServletMultipartResolver multipartResolver() { - StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); - multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); - return multipartResolver; - } - - /** - * 华东机房 - */ - @Bean - public com.qiniu.storage.Configuration qiniuConfig() { - return new com.qiniu.storage.Configuration(Zone.zone0()); - } - - /** - * 构建一个七牛上传工具实例 - */ - @Bean - public UploadManager uploadManager() { - return new UploadManager(qiniuConfig()); - } - - /** - * 认证信息实例 - */ - @Bean - public Auth auth() { - return Auth.create(accessKey, secretKey); - } - - /** - * 构建七牛空间管理实例 - */ - @Bean - public BucketManager bucketManager() { - return new BucketManager(auth(), qiniuConfig()); - } -} -``` - -## UploadController.java - -```java -/** - *

    - * 文件上传 Controller - *

    - * - * @package: com.xkcoding.upload.controller - * @description: 文件上传 Controller - * @author: yangkai.shen - * @date: Created in 2018/11/6 16:33 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@Slf4j -@RequestMapping("/upload") -public class UploadController { - @Value("${spring.servlet.multipart.location}") - private String fileTempPath; - - @Value("${qiniu.prefix}") - private String prefix; - - private final IQiNiuService qiNiuService; - - @Autowired - public UploadController(IQiNiuService qiNiuService) { - this.qiNiuService = qiNiuService; - } - - @PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public Dict local(@RequestParam("file") MultipartFile file) { - if (file.isEmpty()) { - return Dict.create().set("code", 400).set("message", "文件内容为空"); - } - String fileName = file.getOriginalFilename(); - String rawFileName = StrUtil.subBefore(fileName, ".", true); - String fileType = StrUtil.subAfter(fileName, ".", true); - String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; - try { - file.transferTo(new File(localFilePath)); - } catch (IOException e) { - log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - - log.info("【文件上传至本地】绝对路径:{}", localFilePath); - return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath)); - } - - @PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public Dict yun(@RequestParam("file") MultipartFile file) { - if (file.isEmpty()) { - return Dict.create().set("code", 400).set("message", "文件内容为空"); - } - String fileName = file.getOriginalFilename(); - String rawFileName = StrUtil.subBefore(fileName, ".", true); - String fileType = StrUtil.subAfter(fileName, ".", true); - String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; - try { - file.transferTo(new File(localFilePath)); - Response response = qiNiuService.uploadFile(new File(localFilePath)); - if (response.isOK()) { - JSONObject jsonObject = JSONUtil.parseObj(response.bodyString()); - - String yunFileName = jsonObject.getStr("key"); - String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName; - - FileUtil.del(new File(localFilePath)); - - log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath); - return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath)); - } else { - log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response)); - FileUtil.del(new File(localFilePath)); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - } catch (IOException e) { - log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - } -} -``` - -## QiNiuServiceImpl.java - -```java -/** - *

    - * 七牛云上传Service - *

    - * - * @package: com.xkcoding.upload.service.impl - * @description: 七牛云上传Service - * @author: yangkai.shen - * @date: Created in 2018/11/6 17:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { - private final UploadManager uploadManager; - - private final Auth auth; - - @Value("${qiniu.bucket}") - private String bucket; - - private StringMap putPolicy; - - @Autowired - public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) { - this.uploadManager = uploadManager; - this.auth = auth; - } - - /** - * 七牛云上传文件 - * - * @param file 文件 - * @return 七牛上传Response - * @throws QiniuException 七牛异常 - */ - @Override - public Response uploadFile(File file) throws QiniuException { - Response response = this.uploadManager.put(file, file.getName(), getUploadToken()); - int retry = 0; - while (response.needRetry() && retry < 3) { - response = this.uploadManager.put(file, file.getName(), getUploadToken()); - retry++; - } - return response; - } - - @Override - public void afterPropertiesSet() { - this.putPolicy = new StringMap(); - putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}"); - } - - /** - * 获取上传凭证 - * - * @return 上传凭证 - */ - private String getUploadToken() { - return this.auth.uploadToken(bucket, null, 3600, putPolicy); - } -} -``` - -## index.html - -```html - - - - - - - spring-boot-demo-upload - - - - - - - - -
    - - - -

    - - 本地上传 -

    -
    - - 选择文件 - - - {{ local.loadingStatus ? '本地文件上传中' : '本地上传' }} - -
    -
    -
    状态:{{local.log.message}}
    -
    文件名:{{local.log.fileName}}
    -
    文件路径:{{local.log.filePath}}
    -
    -
    -
    - - -

    - - 七牛云上传 -

    -
    - - 选择文件 - - - {{ yun.loadingStatus ? '七牛云文件上传中' : '七牛云上传' }} - -
    -
    -
    状态:{{yun.log.message}}
    -
    文件名:{{yun.log.fileName}}
    -
    文件路径:{{yun.log.filePath}}
    -
    -
    -
    -
    -
    - - - -``` - -## 参考 - -1. Spring 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-multipart-file-upload-configuration -2. 七牛云官方文档:https://developer.qiniu.com/kodo/sdk/1239/java#5 - diff --git a/spring-boot-demo-upload/pom.xml b/spring-boot-demo-upload/pom.xml deleted file mode 100644 index 8d9b5ec..0000000 --- a/spring-boot-demo-upload/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-upload - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-upload - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - com.qiniu - qiniu-java-sdk - [7.2.0, 7.2.99] - - - - - spring-boot-demo-upload - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java deleted file mode 100644 index 2afb7af..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.upload; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动类 - *

    - * - * @package: com.xkcoding.upload - * @description: 启动类 - * @author: shenyangkai - * @date: Created in 2018/10/20 21:23 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: shenyangkai - */ -@SpringBootApplication -public class SpringBootDemoUploadApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoUploadApplication.class, args); - } -} diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java deleted file mode 100644 index c367ca8..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.xkcoding.upload.config; - -import com.qiniu.common.Zone; -import com.qiniu.storage.BucketManager; -import com.qiniu.storage.UploadManager; -import com.qiniu.util.Auth; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.multipart.MultipartResolver; -import org.springframework.web.multipart.support.StandardServletMultipartResolver; -import org.springframework.web.servlet.DispatcherServlet; - -import javax.servlet.MultipartConfigElement; -import javax.servlet.Servlet; - -/** - *

    - * 上传配置 - *

    - * - * @package: com.xkcoding.upload.config - * @description: 上传配置 - * @author: yangkai.shen - * @date: Created in 2018/10/23 14:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class}) -@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) -@EnableConfigurationProperties(MultipartProperties.class) -public class UploadConfig { - @Value("${qiniu.accessKey}") - private String accessKey; - - @Value("${qiniu.secretKey}") - private String secretKey; - - private final MultipartProperties multipartProperties; - - @Autowired - public UploadConfig(MultipartProperties multipartProperties) { - this.multipartProperties = multipartProperties; - } - - /** - * 上传配置 - */ - @Bean - @ConditionalOnMissingBean - public MultipartConfigElement multipartConfigElement() { - return this.multipartProperties.createMultipartConfig(); - } - - /** - * 注册解析器 - */ - @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) - @ConditionalOnMissingBean(MultipartResolver.class) - public StandardServletMultipartResolver multipartResolver() { - StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); - multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); - return multipartResolver; - } - - /** - * 华东机房 - */ - @Bean - public com.qiniu.storage.Configuration qiniuConfig() { - return new com.qiniu.storage.Configuration(Zone.zone0()); - } - - /** - * 构建一个七牛上传工具实例 - */ - @Bean - public UploadManager uploadManager() { - return new UploadManager(qiniuConfig()); - } - - /** - * 认证信息实例 - */ - @Bean - public Auth auth() { - return Auth.create(accessKey, secretKey); - } - - /** - * 构建七牛空间管理实例 - */ - @Bean - public BucketManager bucketManager() { - return new BucketManager(auth(), qiniuConfig()); - } -} diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java deleted file mode 100644 index 003ffc2..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/IndexController.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.upload.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -/** - *

    - * 首页Controller - *

    - * - * @package: com.xkcoding.upload.controller - * @description: 首页Controller - * @author: shenyangkai - * @date: Created in 2018/10/20 21:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: shenyangkai - */ -@Controller -public class IndexController { - @GetMapping("") - public String index() { - return "index"; - } -} diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java deleted file mode 100644 index e31dd42..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.xkcoding.upload.controller; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.qiniu.http.Response; -import com.xkcoding.upload.service.IQiNiuService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; - -/** - *

    - * 文件上传 Controller - *

    - * - * @package: com.xkcoding.upload.controller - * @description: 文件上传 Controller - * @author: yangkai.shen - * @date: Created in 2018/11/6 16:33 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@Slf4j -@RequestMapping("/upload") -public class UploadController { - @Value("${spring.servlet.multipart.location}") - private String fileTempPath; - - @Value("${qiniu.prefix}") - private String prefix; - - private final IQiNiuService qiNiuService; - - @Autowired - public UploadController(IQiNiuService qiNiuService) { - this.qiNiuService = qiNiuService; - } - - @PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public Dict local(@RequestParam("file") MultipartFile file) { - if (file.isEmpty()) { - return Dict.create().set("code", 400).set("message", "文件内容为空"); - } - String fileName = file.getOriginalFilename(); - String rawFileName = StrUtil.subBefore(fileName, ".", true); - String fileType = StrUtil.subAfter(fileName, ".", true); - String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; - try { - file.transferTo(new File(localFilePath)); - } catch (IOException e) { - log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - - log.info("【文件上传至本地】绝对路径:{}", localFilePath); - return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath)); - } - - @PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public Dict yun(@RequestParam("file") MultipartFile file) { - if (file.isEmpty()) { - return Dict.create().set("code", 400).set("message", "文件内容为空"); - } - String fileName = file.getOriginalFilename(); - String rawFileName = StrUtil.subBefore(fileName, ".", true); - String fileType = StrUtil.subAfter(fileName, ".", true); - String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; - try { - file.transferTo(new File(localFilePath)); - Response response = qiNiuService.uploadFile(new File(localFilePath)); - if (response.isOK()) { - JSONObject jsonObject = JSONUtil.parseObj(response.bodyString()); - - String yunFileName = jsonObject.getStr("key"); - String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName; - - FileUtil.del(new File(localFilePath)); - - log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath); - return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath)); - } else { - log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response)); - FileUtil.del(new File(localFilePath)); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - } catch (IOException e) { - log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath); - return Dict.create().set("code", 500).set("message", "文件上传失败"); - } - } -} diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java deleted file mode 100644 index 15a15be..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.upload.service; - -import com.qiniu.common.QiniuException; -import com.qiniu.http.Response; - -import java.io.File; - -/** - *

    - * 七牛云上传Service - *

    - * - * @package: com.xkcoding.upload.service - * @description: 七牛云上传Service - * @author: yangkai.shen - * @date: Created in 2018/11/6 17:21 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface IQiNiuService { - /** - * 七牛云上传文件 - * - * @param file 文件 - * @return 七牛上传Response - * @throws QiniuException 七牛异常 - */ - Response uploadFile(File file) throws QiniuException; -} diff --git a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java b/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java deleted file mode 100644 index 8993e64..0000000 --- a/spring-boot-demo-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.xkcoding.upload.service.impl; - -import com.qiniu.common.QiniuException; -import com.qiniu.http.Response; -import com.qiniu.storage.UploadManager; -import com.qiniu.util.Auth; -import com.qiniu.util.StringMap; -import com.xkcoding.upload.service.IQiNiuService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.io.File; - -/** - *

    - * 七牛云上传Service - *

    - * - * @package: com.xkcoding.upload.service.impl - * @description: 七牛云上传Service - * @author: yangkai.shen - * @date: Created in 2018/11/6 17:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Service -@Slf4j -public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { - private final UploadManager uploadManager; - - private final Auth auth; - - @Value("${qiniu.bucket}") - private String bucket; - - private StringMap putPolicy; - - @Autowired - public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) { - this.uploadManager = uploadManager; - this.auth = auth; - } - - /** - * 七牛云上传文件 - * - * @param file 文件 - * @return 七牛上传Response - * @throws QiniuException 七牛异常 - */ - @Override - public Response uploadFile(File file) throws QiniuException { - Response response = this.uploadManager.put(file, file.getName(), getUploadToken()); - int retry = 0; - while (response.needRetry() && retry < 3) { - response = this.uploadManager.put(file, file.getName(), getUploadToken()); - retry++; - } - return response; - } - - @Override - public void afterPropertiesSet() { - this.putPolicy = new StringMap(); - putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}"); - } - - /** - * 获取上传凭证 - * - * @return 上传凭证 - */ - private String getUploadToken() { - return this.auth.uploadToken(bucket, null, 3600, putPolicy); - } -} diff --git a/spring-boot-demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java b/spring-boot-demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java deleted file mode 100644 index bf477b6..0000000 --- a/spring-boot-demo-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.upload; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoUploadApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-ureport2/pom.xml b/spring-boot-demo-ureport2/pom.xml deleted file mode 100644 index a922faa..0000000 --- a/spring-boot-demo-ureport2/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-ureport2 - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-ureport2 - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-ureport2 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java b/spring-boot-demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java deleted file mode 100644 index 369d64c..0000000 --- a/spring-boot-demo-ureport2/src/main/java/com/xkcoding/ureport2/SpringBootDemoUreport2Application.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.ureport2; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.ureport2 - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-02-26 23:56 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoUreport2Application { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoUreport2Application.class, args); - } - -} - diff --git a/spring-boot-demo-urule/pom.xml b/spring-boot-demo-urule/pom.xml deleted file mode 100644 index 877f84c..0000000 --- a/spring-boot-demo-urule/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-urule - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-urule - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-urule - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java b/spring-boot-demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java deleted file mode 100644 index d9d0dbb..0000000 --- a/spring-boot-demo-urule/src/main/java/com/xkcoding/urule/SpringBootDemoUruleApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.urule; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.urule - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2019-02-25 22:46 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoUruleApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoUruleApplication.class, args); - } - -} - diff --git a/spring-boot-demo-war/README.md b/spring-boot-demo-war/README.md deleted file mode 100644 index 978ceeb..0000000 --- a/spring-boot-demo-war/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# spring-boot-demo-war - -> 本 demo 主要演示了如何将 Spring Boot 项目打包成传统的 war 包程序。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-war - 1.0.0-SNAPSHOT - - war - - spring-boot-demo-war - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-war - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## SpringBootDemoWarApplication.java - -```java -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.war - * @description: 启动器 - * @author: shenyangkai - * @date: Created in 2018/10/30 19:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: shenyangkai - */ -@SpringBootApplication -public class SpringBootDemoWarApplication extends SpringBootServletInitializer { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoWarApplication.class, args); - } - - /** - * 若需要打成 war 包,则需要写一个类继承 {@link SpringBootServletInitializer} 并重写 {@link SpringBootServletInitializer#configure(SpringApplicationBuilder)} - */ - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(SpringBootDemoWarApplication.class); - } -} -``` - -## 参考 - -https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-create-a-deployable-war-file - diff --git a/spring-boot-demo-war/pom.xml b/spring-boot-demo-war/pom.xml deleted file mode 100644 index be8ca17..0000000 --- a/spring-boot-demo-war/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-war - 1.0.0-SNAPSHOT - - war - - spring-boot-demo-war - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - spring-boot-demo-war - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-war-plugin - 2.6 - - false - - - - - - diff --git a/spring-boot-demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java b/spring-boot-demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java deleted file mode 100644 index 2ef1d1b..0000000 --- a/spring-boot-demo-war/src/main/java/com/xkcoding/war/SpringBootDemoWarApplication.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.war; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.war - * @description: 启动器 - * @author: shenyangkai - * @date: Created in 2018/10/30 19:37 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: shenyangkai - */ -@SpringBootApplication -public class SpringBootDemoWarApplication extends SpringBootServletInitializer { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoWarApplication.class, args); - } - - /** - * 若需要打成 war 包,则需要写一个类继承 {@link SpringBootServletInitializer} 并重写 {@link SpringBootServletInitializer#configure(SpringApplicationBuilder)} - */ - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(SpringBootDemoWarApplication.class); - } -} diff --git a/spring-boot-demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java b/spring-boot-demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java deleted file mode 100644 index 5ad505d..0000000 --- a/spring-boot-demo-war/src/test/java/com/xkcoding/war/SpringBootDemoWarApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xkcoding.war; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringBootDemoWarApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-boot-demo-websocket-socketio/README.md b/spring-boot-demo-websocket-socketio/README.md deleted file mode 100644 index 8eaea88..0000000 --- a/spring-boot-demo-websocket-socketio/README.md +++ /dev/null @@ -1,334 +0,0 @@ -# spring-boot-demo-websocket-socketio - -> 此 demo 主要演示了 Spring Boot 如何使用 `netty-socketio` 集成 WebSocket,实现一个简单的聊天室。 - -## 1. 代码 - -### 1.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-websocket-socketio - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-websocket-socketio - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.7.16 - - - - - com.corundumstudio.socketio - netty-socketio - ${netty-socketio.version} - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-websocket-socketio - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 1.2. ServerConfig.java - -> websocket服务器配置,包括服务器IP、端口信息、以及连接认证等配置 - -```java -/** - *

    - * websocket服务器配置 - *

    - * - * @package: com.xkcoding.websocket.socketio.config - * @description: websocket服务器配置 - * @author: yangkai.shen - * @date: Created in 2018-12-18 16:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties({WsConfig.class}) -public class ServerConfig { - - @Bean - public SocketIOServer server(WsConfig wsConfig) { - com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); - config.setHostname(wsConfig.getHost()); - config.setPort(wsConfig.getPort()); - - //这个listener可以用来进行身份验证 - config.setAuthorizationListener(data -> { - // http://localhost:8081?token=xxxxxxx - // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证 - String token = data.getSingleUrlParam("token"); - // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil - // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件 - return StrUtil.isNotBlank(token); - }); - - return new SocketIOServer(config); - } - - /** - * Spring 扫描自定义注解 - */ - @Bean - public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) { - return new SpringAnnotationScanner(server); - } -} -``` - -### 1.3. MessageEventHandler.java - -> 核心事件处理类,主要处理客户端发起的消息事件,以及主动往客户端发起事件 - -```java -/** - *

    - * 消息事件处理 - *

    - * - * @package: com.xkcoding.websocket.socketio.handler - * @description: 消息事件处理 - * @author: yangkai.shen - * @date: Created in 2018-12-18 18:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class MessageEventHandler { - @Autowired - private SocketIOServer server; - - @Autowired - private DbTemplate dbTemplate; - - /** - * 添加connect事件,当客户端发起连接时调用 - * - * @param client 客户端对象 - */ - @OnConnect - public void onConnect(SocketIOClient client) { - if (client != null) { - String token = client.getHandshakeData().getSingleUrlParam("token"); - // 模拟用户id 和token一致 - String userId = client.getHandshakeData().getSingleUrlParam("token"); - UUID sessionId = client.getSessionId(); - - dbTemplate.save(userId, sessionId); - log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId); - } else { - log.error("客户端为空"); - } - } - - /** - * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息 - * - * @param client 客户端对象 - */ - @OnDisconnect - public void onDisconnect(SocketIOClient client) { - if (client != null) { - String token = client.getHandshakeData().getSingleUrlParam("token"); - // 模拟用户id 和token一致 - String userId = client.getHandshakeData().getSingleUrlParam("token"); - UUID sessionId = client.getSessionId(); - - dbTemplate.deleteByUserId(userId); - log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId); - client.disconnect(); - } else { - log.error("客户端为空"); - } - } - - /** - * 加入群聊 - * - * @param client 客户端 - * @param request 请求 - * @param data 群聊 - */ - @OnEvent(value = Event.JOIN) - public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) { - log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId()); - client.joinRoom(data.getGroupId()); - - server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data); - } - - - @OnEvent(value = Event.CHAT) - public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) { - Optional toUser = dbTemplate.findByUserId(data.getToUid()); - if (toUser.isPresent()) { - log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage()); - sendToSingle(toUser.get(), data); - client.sendEvent(Event.CHAT_RECEIVED, "发送成功"); - } else { - client.sendEvent(Event.CHAT_REFUSED, "发送失败,对方不想理你"); - } - } - - @OnEvent(value = Event.GROUP) - public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) { - Collection clients = server.getRoomOperations(data.getGroupId()).getClients(); - - boolean inGroup = false; - for (SocketIOClient socketIOClient : clients) { - if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) { - inGroup = true; - break; - } - } - if (inGroup) { - log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage()); - sendToGroup(data); - } else { - request.sendAckData("请先加群!"); - } - } - - /** - * 单聊 - */ - public void sendToSingle(UUID sessionId, SingleMessageRequest message) { - server.getClient(sessionId).sendEvent(Event.CHAT, message); - } - - /** - * 广播 - */ - public void sendToBroadcast(BroadcastMessageRequest message) { - log.info("系统紧急广播一条通知:{}", message.getMessage()); - for (UUID clientId : dbTemplate.findAll()) { - if (server.getClient(clientId) == null) { - continue; - } - server.getClient(clientId).sendEvent(Event.BROADCAST, message); - } - } - - /** - * 群聊 - */ - public void sendToGroup(GroupMessageRequest message) { - server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message); - } -} -``` - -### 1.4. ServerRunner.java - -> websocket 服务器启动类 - -```java -/** - *

    - * websocket服务器启动 - *

    - * - * @package: com.xkcoding.websocket.socketio.init - * @description: websocket服务器启动 - * @author: yangkai.shen - * @date: Created in 2018-12-18 17:07 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class ServerRunner implements CommandLineRunner { - @Autowired - private SocketIOServer server; - - @Override - public void run(String... args) { - server.start(); - log.info("websocket 服务器启动成功。。。"); - } -} -``` - -## 2. 运行方式 - -1. 启动 `SpringBootDemoWebsocketSocketioApplication.java` -2. 使用不同的浏览器,访问 http://localhost:8080/demo/index.html - -## 3. 运行效果 - -**浏览器1:**![image-20181219152318079](http://static.xkcoding.com/spring-boot-demo/websocket/socketio/064155.jpg) - -**浏览器2:**![image-20181219152330156](http://static.xkcoding.com/spring-boot-demo/websocket/socketio/064154.jpg) - -## 4. 参考 - -### 4.1. 后端 - -1. Netty-socketio 官方仓库:https://github.com/mrniko/netty-socketio -2. SpringBoot系列 - 集成SocketIO实时通信:https://www.xncoding.com/2017/07/16/spring/sb-socketio.html -3. Spring Boot 集成 socket.io 后端实现消息实时通信:http://alexpdh.com/2017/09/03/springboot-socketio/ -4. Spring Boot实战之netty-socketio实现简单聊天室:http://blog.csdn.net/sun_t89/article/details/52060946 - -### 4.2. 前端 - -1. socket.io 官网:https://socket.io/ -2. axios.js 用法:https://github.com/axios/axios#example \ No newline at end of file diff --git a/spring-boot-demo-websocket-socketio/pom.xml b/spring-boot-demo-websocket-socketio/pom.xml deleted file mode 100644 index 31e5221..0000000 --- a/spring-boot-demo-websocket-socketio/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-websocket-socketio - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-websocket-socketio - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 1.7.16 - - - - - com.corundumstudio.socketio - netty-socketio - ${netty-socketio.version} - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-websocket-socketio - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java deleted file mode 100644 index c604527..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/SpringBootDemoWebsocketSocketioApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xkcoding.websocket.socketio; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.websocket.socketio - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-12 13:59 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoWebsocketSocketioApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoWebsocketSocketioApplication.class, args); - } -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java deleted file mode 100644 index 225187a..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/DbTemplate.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.xkcoding.websocket.socketio.config; - -import cn.hutool.core.collection.CollUtil; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -/** - *

    - * 模拟数据库 - *

    - * - * @package: com.xkcoding.websocket.socketio.config - * @description: 模拟数据库 - * @author: yangkai.shen - * @date: Created in 2018-12-18 19:12 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -public class DbTemplate { - /** - * 模拟数据库存储 user_id <-> session_id 的关系 - */ - public static final ConcurrentHashMap DB = new ConcurrentHashMap<>(); - - /** - * 获取所有SessionId - * - * @return SessionId列表 - */ - public List findAll() { - return CollUtil.newArrayList(DB.values()); - } - - /** - * 根据UserId查询SessionId - * - * @param userId 用户id - * @return SessionId - */ - public Optional findByUserId(String userId) { - return Optional.ofNullable(DB.get(userId)); - } - - /** - * 保存/更新 user_id <-> session_id 的关系 - * - * @param userId 用户id - * @param sessionId SessionId - */ - public void save(String userId, UUID sessionId) { - DB.put(userId, sessionId); - } - - /** - * 删除 user_id <-> session_id 的关系 - * - * @param userId 用户id - */ - public void deleteByUserId(String userId) { - DB.remove(userId); - } - -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java deleted file mode 100644 index 75caa7f..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/Event.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.websocket.socketio.config; - -/** - *

    - * 事件常量 - *

    - * - * @package: com.xkcoding.websocket.socketio.config - * @description: 事件常量 - * @author: yangkai.shen - * @date: Created in 2018-12-18 19:36 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface Event { - /** - * 聊天事件 - */ - String CHAT = "chat" ; - - /** - * 广播消息 - */ - String BROADCAST = "broadcast" ; - - /** - * 群聊 - */ - String GROUP = "group" ; - - /** - * 加入群聊 - */ - String JOIN = "join" ; - -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java deleted file mode 100644 index 8baccc1..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/ServerConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.xkcoding.websocket.socketio.config; - -import cn.hutool.core.util.StrUtil; -import com.corundumstudio.socketio.SocketIOServer; -import com.corundumstudio.socketio.annotation.SpringAnnotationScanner; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * websocket服务器配置 - *

    - * - * @package: com.xkcoding.websocket.socketio.config - * @description: websocket服务器配置 - * @author: yangkai.shen - * @date: Created in 2018-12-18 16:42 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties({WsConfig.class}) -public class ServerConfig { - - @Bean - public SocketIOServer server(WsConfig wsConfig) { - com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); - config.setHostname(wsConfig.getHost()); - config.setPort(wsConfig.getPort()); - - //这个listener可以用来进行身份验证 - config.setAuthorizationListener(data -> { - // http://localhost:8081?token=xxxxxxx - // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证 - String token = data.getSingleUrlParam("token"); - // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil - // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件 - return StrUtil.isNotBlank(token); - }); - - return new SocketIOServer(config); - } - - /** - * Spring 扫描自定义注解 - */ - @Bean - public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) { - return new SpringAnnotationScanner(server); - } -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java deleted file mode 100644 index a94505c..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/config/WsConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xkcoding.websocket.socketio.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - *

    - * WebSocket配置类 - *

    - * - * @package: com.xkcoding.websocket.socketio.config - * @description: WebSocket配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-18 16:41 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@ConfigurationProperties(prefix = "ws.server") -@Data -public class WsConfig { - /** - * 端口号 - */ - private Integer port; - - /** - * host - */ - private String host; -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java deleted file mode 100644 index ed94ffb..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/controller/MessageController.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xkcoding.websocket.socketio.controller; - -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import com.xkcoding.websocket.socketio.handler.MessageEventHandler; -import com.xkcoding.websocket.socketio.payload.BroadcastMessageRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.lang.reflect.Field; - -/** - *

    - * 消息发送Controller - *

    - * - * @package: com.xkcoding.websocket.socketio.controller - * @description: 消息发送Controller - * @author: yangkai.shen - * @date: Created in 2018-12-18 19:50 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/send") -@Slf4j -public class MessageController { - @Autowired - private MessageEventHandler messageHandler; - - @PostMapping("/broadcast") - public Dict broadcast(@RequestBody BroadcastMessageRequest message) { - if (isBlank(message)) { - return Dict.create().set("flag", false).set("code", 400).set("message", "参数为空"); - } - messageHandler.sendToBroadcast(message); - return Dict.create().set("flag", true).set("code", 200).set("message", "发送成功"); - } - - /** - * 判断Bean是否为空对象或者空白字符串,空对象表示本身为null或者所有属性都为null - * - * @param bean Bean对象 - * @return 是否为空,true - 空 / false - 非空 - */ - private boolean isBlank(Object bean) { - if (null != bean) { - for (Field field : ReflectUtil.getFields(bean.getClass())) { - Object fieldValue = ReflectUtil.getFieldValue(bean, field); - if (null != fieldValue) { - if (fieldValue instanceof String && StrUtil.isNotBlank((String) fieldValue)) { - return false; - } else if (!(fieldValue instanceof String)) { - return false; - } - } - } - } - return true; - } - -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java deleted file mode 100644 index 3610115..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/handler/MessageEventHandler.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.xkcoding.websocket.socketio.handler; - -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.ObjectUtil; -import com.corundumstudio.socketio.AckRequest; -import com.corundumstudio.socketio.SocketIOClient; -import com.corundumstudio.socketio.SocketIOServer; -import com.corundumstudio.socketio.annotation.OnConnect; -import com.corundumstudio.socketio.annotation.OnDisconnect; -import com.corundumstudio.socketio.annotation.OnEvent; -import com.xkcoding.websocket.socketio.config.DbTemplate; -import com.xkcoding.websocket.socketio.config.Event; -import com.xkcoding.websocket.socketio.payload.BroadcastMessageRequest; -import com.xkcoding.websocket.socketio.payload.GroupMessageRequest; -import com.xkcoding.websocket.socketio.payload.JoinRequest; -import com.xkcoding.websocket.socketio.payload.SingleMessageRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Collection; -import java.util.Optional; -import java.util.UUID; - -/** - *

    - * 消息事件处理 - *

    - * - * @package: com.xkcoding.websocket.socketio.handler - * @description: 消息事件处理 - * @author: yangkai.shen - * @date: Created in 2018-12-18 18:57 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class MessageEventHandler { - @Autowired - private SocketIOServer server; - - @Autowired - private DbTemplate dbTemplate; - - /** - * 添加connect事件,当客户端发起连接时调用 - * - * @param client 客户端对象 - */ - @OnConnect - public void onConnect(SocketIOClient client) { - if (client != null) { - String token = client.getHandshakeData().getSingleUrlParam("token"); - // 模拟用户id 和token一致 - String userId = client.getHandshakeData().getSingleUrlParam("token"); - UUID sessionId = client.getSessionId(); - - dbTemplate.save(userId, sessionId); - log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId); - } else { - log.error("客户端为空"); - } - } - - /** - * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息 - * - * @param client 客户端对象 - */ - @OnDisconnect - public void onDisconnect(SocketIOClient client) { - if (client != null) { - String token = client.getHandshakeData().getSingleUrlParam("token"); - // 模拟用户id 和token一致 - String userId = client.getHandshakeData().getSingleUrlParam("token"); - UUID sessionId = client.getSessionId(); - - dbTemplate.deleteByUserId(userId); - log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId); - client.disconnect(); - } else { - log.error("客户端为空"); - } - } - - /** - * 加入群聊 - * - * @param client 客户端 - * @param request 请求 - * @param data 群聊 - */ - @OnEvent(value = Event.JOIN) - public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) { - log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId()); - client.joinRoom(data.getGroupId()); - - server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data); - } - - - @OnEvent(value = Event.CHAT) - public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) { - Optional toUser = dbTemplate.findByUserId(data.getToUid()); - if (toUser.isPresent()) { - log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage()); - sendToSingle(toUser.get(), data); - request.sendAckData(Dict.create().set("flag", true).set("message", "发送成功")); - } else { - request.sendAckData(Dict.create() - .set("flag", false) - .set("message", "发送失败,对方不想理你(" + data.getToUid() + "不在线)")); - } - } - - @OnEvent(value = Event.GROUP) - public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) { - Collection clients = server.getRoomOperations(data.getGroupId()).getClients(); - - boolean inGroup = false; - for (SocketIOClient socketIOClient : clients) { - if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) { - inGroup = true; - break; - } - } - if (inGroup) { - log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage()); - sendToGroup(data); - } else { - request.sendAckData("请先加群!"); - } - } - - /** - * 单聊 - */ - public void sendToSingle(UUID sessionId, SingleMessageRequest message) { - server.getClient(sessionId).sendEvent(Event.CHAT, message); - } - - /** - * 广播 - */ - public void sendToBroadcast(BroadcastMessageRequest message) { - log.info("系统紧急广播一条通知:{}", message.getMessage()); - for (UUID clientId : dbTemplate.findAll()) { - if (server.getClient(clientId) == null) { - continue; - } - server.getClient(clientId).sendEvent(Event.BROADCAST, message); - } - } - - /** - * 群聊 - */ - public void sendToGroup(GroupMessageRequest message) { - server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message); - } -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java deleted file mode 100644 index cb548b5..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/init/ServerRunner.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xkcoding.websocket.socketio.init; - -import com.corundumstudio.socketio.SocketIOServer; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -/** - *

    - * websocket服务器启动 - *

    - * - * @package: com.xkcoding.websocket.socketio.init - * @description: websocket服务器启动 - * @author: yangkai.shen - * @date: Created in 2018-12-18 17:07 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Component -@Slf4j -public class ServerRunner implements CommandLineRunner { - @Autowired - private SocketIOServer server; - - @Override - public void run(String... args) { - server.start(); - log.info("websocket 服务器启动成功。。。"); - } -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java deleted file mode 100644 index 7fe9bb3..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/BroadcastMessageRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xkcoding.websocket.socketio.payload; - -import lombok.Data; - -/** - *

    - * 广播消息载荷 - *

    - * - * @package: com.xkcoding.websocket.socketio.payload - * @description: 广播消息载荷 - * @author: yangkai.shen - * @date: Created in 2018-12-18 20:01 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class BroadcastMessageRequest { - /** - * 消息内容 - */ - private String message; -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java deleted file mode 100644 index 5670b41..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/GroupMessageRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.websocket.socketio.payload; - -import lombok.Data; - -/** - *

    - * 群聊消息载荷 - *

    - * - * @package: com.xkcoding.websocket.socketio.payload - * @description: 群聊消息载荷 - * @author: yangkai.shen - * @date: Created in 2018-12-18 16:59 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class GroupMessageRequest { - /** - * 消息发送方用户id - */ - private String fromUid; - - /** - * 群组id - */ - private String groupId; - - /** - * 消息内容 - */ - private String message; -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java deleted file mode 100644 index ef63fc8..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/JoinRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.websocket.socketio.payload; - -import lombok.Data; - -/** - *

    - * 加群载荷 - *

    - * - * @package: com.xkcoding.websocket.socketio.payload - * @description: 加群载荷 - * @author: yangkai.shen - * @date: Created in 2018-12-19 13:36 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class JoinRequest { - /** - * 用户id - */ - private String userId; - - /** - * 群名称 - */ - private String groupId; -} diff --git a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java b/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java deleted file mode 100644 index 5998b83..0000000 --- a/spring-boot-demo-websocket-socketio/src/main/java/com/xkcoding/websocket/socketio/payload/SingleMessageRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.xkcoding.websocket.socketio.payload; - -import lombok.Data; - -/** - *

    - * 私聊消息载荷 - *

    - * - * @package: com.xkcoding.websocket.socketio.payload - * @description: 私聊消息载荷 - * @author: yangkai.shen - * @date: Created in 2018-12-18 17:02 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class SingleMessageRequest { - /** - * 消息发送方用户id - */ - private String fromUid; - - /** - * 消息接收方用户id - */ - private String toUid; - - /** - * 消息内容 - */ - private String message; -} diff --git a/spring-boot-demo-websocket/README.md b/spring-boot-demo-websocket/README.md deleted file mode 100644 index dc81fce..0000000 --- a/spring-boot-demo-websocket/README.md +++ /dev/null @@ -1,389 +0,0 @@ -# spring-boot-demo-websocket - -> 此 demo 主要演示了 Spring Boot 如何集成 WebSocket,实现后端主动往前端推送数据。网上大部分websocket的例子都是聊天室,本例主要是推送服务器状态信息。前端页面基于vue和element-ui实现。 - -## 1. 代码 - -### 1.1. pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-websocket - 1.0.0-SNAPSHOT - - spring-boot-demo-websocket - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.9.1 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-websocket - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.github.oshi - oshi-core - ${oshi.version} - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-websocket - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -### 1.2. WebSocketConfig.java - -```java -/** - *

    - * WebSocket配置 - *

    - * - * @package: com.xkcoding.websocket.config - * @description: WebSocket配置 - * @author: yangkai.shen - * @date: Created in 2018-12-14 15:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableWebSocket -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // 注册一个 /notification 端点,前端通过这个端点进行连接 - registry.addEndpoint("/notification") - //解决跨域问题 - .setAllowedOrigins("*") - .withSockJS(); - } - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 - registry.enableSimpleBroker("/topic"); - } - -} -``` - -### 1.3. 服务器相关实体 - -> 此部分实体 参见包路径 [com.xkcoding.websocket.model](./src/main/java/com/xkcoding/websocket/model) - -### 1.4. ServerTask.java - -```java -/** - *

    - * 服务器定时推送任务 - *

    - * - * @package: com.xkcoding.websocket.task - * @description: 服务器定时推送任务 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:04 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Component -public class ServerTask { - @Autowired - private SimpMessagingTemplate wsTemplate; - - /** - * 按照标准时间来算,每隔 2s 执行一次 - */ - @Scheduled(cron = "0/2 * * * * ?") - public void websocket() throws Exception { - log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date())); - // 查询服务器状态 - Server server = new Server(); - server.copyTo(); - ServerVO serverVO = ServerUtil.wrapServerVO(server); - Dict dict = ServerUtil.wrapServerDict(serverVO); - wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict)); - log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date())); - } -} -``` - -### 1.5. server.html - -```html - - - - - 服务器信息 - - - - -
    - - - 手动连接 - 断开连接 - - - - - -
    - CPU信息 -
    - - - - - - -
    -
    - - -
    - 内存信息 -
    - - - - - - -
    -
    -
    - - - -
    - 服务器信息 -
    - - - - - - -
    -
    -
    - - - -
    - Java虚拟机信息 -
    - - - - - - -
    -
    -
    - - - -
    - 磁盘状态 -
    -
    - - - - - - -
    -
    -
    -
    -
    -
    -
    - - - - - - - - -``` - -## 2. 运行方式 - -1. 启动 `SpringBootDemoWebsocketApplication.java` -2. 访问 http://localhost:8080/demo/server.html - -## 3. 运行效果 - -![image-20181217110240322](http://static.xkcoding.com/spring-boot-demo/websocket/064107.jpg) - -![image-20181217110304065](http://static.xkcoding.com/spring-boot-demo/websocket/064108.jpg) - -![image-20181217110328810](http://static.xkcoding.com/spring-boot-demo/websocket/064109.jpg) - -![image-20181217110336017](http://static.xkcoding.com/spring-boot-demo/websocket/064109-1.jpg) - -## 4. 参考 - -### 4.1. 后端 - -1. Spring Boot 整合 Websocket 官方文档:https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/web.html#websocket -2. 服务器信息采集 oshi 使用:https://github.com/oshi/oshi - -### 4.2. 前端 - -1. vue.js 语法:https://cn.vuejs.org/v2/guide/ -2. element-ui 用法:http://element-cn.eleme.io/#/zh-CN -3. stomp.js 用法:https://github.com/jmesnil/stomp-websocket -4. sockjs 用法:https://github.com/sockjs/sockjs-client -5. axios.js 用法:https://github.com/axios/axios#example \ No newline at end of file diff --git a/spring-boot-demo-websocket/pom.xml b/spring-boot-demo-websocket/pom.xml deleted file mode 100644 index 6a6babe..0000000 --- a/spring-boot-demo-websocket/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-websocket - 1.0.0-SNAPSHOT - - spring-boot-demo-websocket - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - 3.9.1 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-websocket - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.github.oshi - oshi-core - ${oshi.version} - - - - cn.hutool - hutool-all - - - - com.google.guava - guava - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-websocket - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java deleted file mode 100644 index 6351666..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/SpringBootDemoWebsocketApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xkcoding.websocket; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.websocket - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-14 14:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -@EnableScheduling -public class SpringBootDemoWebsocketApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoWebsocketApplication.class, args); - } - -} - diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java deleted file mode 100644 index 8b577e2..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/common/WebSocketConsts.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.xkcoding.websocket.common; - -/** - *

    - * WebSocket常量 - *

    - * - * @package: com.xkcoding.websocket.common - * @description: WebSocket常量 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:01 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public interface WebSocketConsts { - String PUSH_SERVER = "/topic/server"; -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java deleted file mode 100644 index 47de6d4..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/config/WebSocketConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.websocket.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -/** - *

    - * WebSocket配置 - *

    - * - * @package: com.xkcoding.websocket.config - * @description: WebSocket配置 - * @author: yangkai.shen - * @date: Created in 2018-12-14 15:58 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableWebSocket -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // 注册一个 /notification 端点,前端通过这个端点进行连接 - registry.addEndpoint("/notification") - //解决跨域问题 - .setAllowedOrigins("*") - .withSockJS(); - } - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 - registry.enableSimpleBroker("/topic"); - } - -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java deleted file mode 100644 index b4c61e8..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/controller/ServerController.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.websocket.controller; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.websocket.model.Server; -import com.xkcoding.websocket.payload.ServerVO; -import com.xkcoding.websocket.util.ServerUtil; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - *

    - * 服务器监控Controller - *

    - * - * @package: com.xkcoding.websocket.controller - * @description: 服务器监控Controller - * @author: yangkai.shen - * @date: Created in 2018-12-17 10:22 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@RestController -@RequestMapping("/server") -public class ServerController { - - @GetMapping - public Dict serverInfo() throws Exception { - Server server = new Server(); - server.copyTo(); - ServerVO serverVO = ServerUtil.wrapServerVO(server); - return ServerUtil.wrapServerDict(serverVO); - } - -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java deleted file mode 100644 index 8d51a73..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/Server.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.xkcoding.websocket.model; - -import cn.hutool.core.util.NumberUtil; -import com.xkcoding.websocket.model.server.*; -import com.xkcoding.websocket.util.IpUtil; -import oshi.SystemInfo; -import oshi.hardware.CentralProcessor; -import oshi.hardware.CentralProcessor.TickType; -import oshi.hardware.GlobalMemory; -import oshi.hardware.HardwareAbstractionLayer; -import oshi.software.os.FileSystem; -import oshi.software.os.OSFileStore; -import oshi.software.os.OperatingSystem; -import oshi.util.Util; - -import java.net.UnknownHostException; -import java.util.LinkedList; -import java.util.List; -import java.util.Properties; - -/** - *

    - * 服务器相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model - * @description: 服务器相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class Server { - - private static final int OSHI_WAIT_SECOND = 1000; - - /** - * CPU相关信息 - */ - private Cpu cpu = new Cpu(); - - /** - * 內存相关信息 - */ - private Mem mem = new Mem(); - - /** - * JVM相关信息 - */ - private Jvm jvm = new Jvm(); - - /** - * 服务器相关信息 - */ - private Sys sys = new Sys(); - - /** - * 磁盘相关信息 - */ - private List sysFiles = new LinkedList(); - - public Cpu getCpu() { - return cpu; - } - - public void setCpu(Cpu cpu) { - this.cpu = cpu; - } - - public Mem getMem() { - return mem; - } - - public void setMem(Mem mem) { - this.mem = mem; - } - - public Jvm getJvm() { - return jvm; - } - - public void setJvm(Jvm jvm) { - this.jvm = jvm; - } - - public Sys getSys() { - return sys; - } - - public void setSys(Sys sys) { - this.sys = sys; - } - - public List getSysFiles() { - return sysFiles; - } - - public void setSysFiles(List sysFiles) { - this.sysFiles = sysFiles; - } - - public void copyTo() throws Exception { - SystemInfo si = new SystemInfo(); - HardwareAbstractionLayer hal = si.getHardware(); - - setCpuInfo(hal.getProcessor()); - - setMemInfo(hal.getMemory()); - - setSysInfo(); - - setJvmInfo(); - - setSysFiles(si.getOperatingSystem()); - } - - /** - * 设置CPU信息 - */ - private void setCpuInfo(CentralProcessor processor) { - // CPU信息 - long[] prevTicks = processor.getSystemCpuLoadTicks(); - Util.sleep(OSHI_WAIT_SECOND); - long[] ticks = processor.getSystemCpuLoadTicks(); - long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; - long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; - long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; - long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; - long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; - long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; - long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; - long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; - long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; - cpu.setCpuNum(processor.getLogicalProcessorCount()); - cpu.setTotal(totalCpu); - cpu.setSys(cSys); - cpu.setUsed(user); - cpu.setWait(iowait); - cpu.setFree(idle); - } - - /** - * 设置内存信息 - */ - private void setMemInfo(GlobalMemory memory) { - mem.setTotal(memory.getTotal()); - mem.setUsed(memory.getTotal() - memory.getAvailable()); - mem.setFree(memory.getAvailable()); - } - - /** - * 设置服务器信息 - */ - private void setSysInfo() { - Properties props = System.getProperties(); - sys.setComputerName(IpUtil.getHostName()); - sys.setComputerIp(IpUtil.getHostIp()); - sys.setOsName(props.getProperty("os.name")); - sys.setOsArch(props.getProperty("os.arch")); - sys.setUserDir(props.getProperty("user.dir")); - } - - /** - * 设置Java虚拟机 - */ - private void setJvmInfo() throws UnknownHostException { - Properties props = System.getProperties(); - jvm.setTotal(Runtime.getRuntime().totalMemory()); - jvm.setMax(Runtime.getRuntime().maxMemory()); - jvm.setFree(Runtime.getRuntime().freeMemory()); - jvm.setVersion(props.getProperty("java.version")); - jvm.setHome(props.getProperty("java.home")); - } - - /** - * 设置磁盘信息 - */ - private void setSysFiles(OperatingSystem os) { - FileSystem fileSystem = os.getFileSystem(); - OSFileStore[] fsArray = fileSystem.getFileStores(); - for (OSFileStore fs : fsArray) { - long free = fs.getUsableSpace(); - long total = fs.getTotalSpace(); - long used = total - free; - SysFile sysFile = new SysFile(); - sysFile.setDirName(fs.getMount()); - sysFile.setSysTypeName(fs.getType()); - sysFile.setTypeName(fs.getName()); - sysFile.setTotal(convertFileSize(total)); - sysFile.setFree(convertFileSize(free)); - sysFile.setUsed(convertFileSize(used)); - sysFile.setUsage(NumberUtil.mul(NumberUtil.div(used, total, 4), 100)); - sysFiles.add(sysFile); - } - } - - /** - * 字节转换 - * - * @param size 字节大小 - * @return 转换后值 - */ - public String convertFileSize(long size) { - long kb = 1024; - long mb = kb * 1024; - long gb = mb * 1024; - if (size >= gb) { - return String.format("%.1f GB", (float) size / gb); - } else if (size >= mb) { - float f = (float) size / mb; - return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); - } else if (size >= kb) { - float f = (float) size / kb; - return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); - } else { - return String.format("%d B", size); - } - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java deleted file mode 100644 index d84cb19..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Cpu.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.xkcoding.websocket.model.server; - -import cn.hutool.core.util.NumberUtil; - -/** - *

    - * CPU相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model.server - * @description: CPU相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class Cpu { - /** - * 核心数 - */ - private int cpuNum; - - /** - * CPU总的使用率 - */ - private double total; - - /** - * CPU系统使用率 - */ - private double sys; - - /** - * CPU用户使用率 - */ - private double used; - - /** - * CPU当前等待率 - */ - private double wait; - - /** - * CPU当前空闲率 - */ - private double free; - - public int getCpuNum() { - return cpuNum; - } - - public void setCpuNum(int cpuNum) { - this.cpuNum = cpuNum; - } - - public double getTotal() { - return NumberUtil.round(NumberUtil.mul(total, 100), 2) - .doubleValue(); - } - - public void setTotal(double total) { - this.total = total; - } - - public double getSys() { - return NumberUtil.round(NumberUtil.mul(sys / total, 100), 2) - .doubleValue(); - } - - public void setSys(double sys) { - this.sys = sys; - } - - public double getUsed() { - return NumberUtil.round(NumberUtil.mul(used / total, 100), 2) - .doubleValue(); - } - - public void setUsed(double used) { - this.used = used; - } - - public double getWait() { - return NumberUtil.round(NumberUtil.mul(wait / total, 100), 2) - .doubleValue(); - } - - public void setWait(double wait) { - this.wait = wait; - } - - public double getFree() { - return NumberUtil.round(NumberUtil.mul(free / total, 100), 2) - .doubleValue(); - } - - public void setFree(double free) { - this.free = free; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java deleted file mode 100644 index a0b770b..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Jvm.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.xkcoding.websocket.model.server; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.NumberUtil; - -import java.lang.management.ManagementFactory; -import java.util.Date; - -/** - *

    - * JVM相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model.server - * @description: JVM相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class Jvm { - /** - * 当前JVM占用的内存总数(M) - */ - private double total; - - /** - * JVM最大可用内存总数(M) - */ - private double max; - - /** - * JVM空闲内存(M) - */ - private double free; - - /** - * JDK版本 - */ - private String version; - - /** - * JDK路径 - */ - private String home; - - /** - * JDK启动时间 - */ - private String startTime; - - /** - * JDK运行时间 - */ - private String runTime; - - public double getTotal() { - return NumberUtil.div(total, (1024 * 1024), 2); - } - - public void setTotal(double total) { - this.total = total; - } - - public double getMax() { - return NumberUtil.div(max, (1024 * 1024), 2); - } - - public void setMax(double max) { - this.max = max; - } - - public double getFree() { - return NumberUtil.div(free, (1024 * 1024), 2); - } - - public void setFree(double free) { - this.free = free; - } - - public double getUsed() { - return NumberUtil.div(total - free, (1024 * 1024), 2); - } - - public double getUsage() { - return NumberUtil.mul(NumberUtil.div(total - free, total, 4), 100); - } - - /** - * 获取JDK名称 - */ - public String getName() { - return ManagementFactory.getRuntimeMXBean() - .getVmName(); - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getHome() { - return home; - } - - public void setHome(String home) { - this.home = home; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getStartTime() { - return DateUtil.formatDateTime(new Date(ManagementFactory.getRuntimeMXBean() - .getStartTime())); - } - - - public void setRunTime(String runTime) { - this.runTime = runTime; - } - - public String getRunTime() { - long startTime = ManagementFactory.getRuntimeMXBean() - .getStartTime(); - return DateUtil.formatBetween(DateUtil.current(false) - startTime); - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java deleted file mode 100644 index 0b72bf4..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Mem.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.xkcoding.websocket.model.server; - -import cn.hutool.core.util.NumberUtil; - -/** - *

    - * 內存相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model.server - * @description: 內存相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:09 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class Mem { - /** - * 内存总量 - */ - private double total; - - /** - * 已用内存 - */ - private double used; - - /** - * 剩余内存 - */ - private double free; - - public double getTotal() { - return NumberUtil.div(total, (1024 * 1024 * 1024), 2); - } - - public void setTotal(long total) { - this.total = total; - } - - public double getUsed() { - return NumberUtil.div(used, (1024 * 1024 * 1024), 2); - } - - public void setUsed(long used) { - this.used = used; - } - - public double getFree() { - return NumberUtil.div(free, (1024 * 1024 * 1024), 2); - } - - public void setFree(long free) { - this.free = free; - } - - public double getUsage() { - return NumberUtil.mul(NumberUtil.div(used, total, 4), 100); - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java deleted file mode 100644 index f2321cb..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/Sys.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.xkcoding.websocket.model.server; - -/** - *

    - * 系统相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model.server - * @description: 系统相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class Sys { - /** - * 服务器名称 - */ - private String computerName; - - /** - * 服务器Ip - */ - private String computerIp; - - /** - * 项目路径 - */ - private String userDir; - - /** - * 操作系统 - */ - private String osName; - - /** - * 系统架构 - */ - private String osArch; - - public String getComputerName() { - return computerName; - } - - public void setComputerName(String computerName) { - this.computerName = computerName; - } - - public String getComputerIp() { - return computerIp; - } - - public void setComputerIp(String computerIp) { - this.computerIp = computerIp; - } - - public String getUserDir() { - return userDir; - } - - public void setUserDir(String userDir) { - this.userDir = userDir; - } - - public String getOsName() { - return osName; - } - - public void setOsName(String osName) { - this.osName = osName; - } - - public String getOsArch() { - return osArch; - } - - public void setOsArch(String osArch) { - this.osArch = osArch; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java deleted file mode 100644 index 823cf75..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/model/server/SysFile.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.xkcoding.websocket.model.server; - -/** - *

    - * 系统文件相关信息实体 - *

    - * - * @package: com.xkcoding.websocket.model.server - * @description: 系统文件相关信息实体 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:10 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class SysFile { - /** - * 盘符路径 - */ - private String dirName; - - /** - * 盘符类型 - */ - private String sysTypeName; - - /** - * 文件类型 - */ - private String typeName; - - /** - * 总大小 - */ - private String total; - - /** - * 剩余大小 - */ - private String free; - - /** - * 已经使用量 - */ - private String used; - - /** - * 资源的使用率 - */ - private double usage; - - public String getDirName() { - return dirName; - } - - public void setDirName(String dirName) { - this.dirName = dirName; - } - - public String getSysTypeName() { - return sysTypeName; - } - - public void setSysTypeName(String sysTypeName) { - this.sysTypeName = sysTypeName; - } - - public String getTypeName() { - return typeName; - } - - public void setTypeName(String typeName) { - this.typeName = typeName; - } - - public String getTotal() { - return total; - } - - public void setTotal(String total) { - this.total = total; - } - - public String getFree() { - return free; - } - - public void setFree(String free) { - this.free = free; - } - - public String getUsed() { - return used; - } - - public void setUsed(String used) { - this.used = used; - } - - public double getUsage() { - return usage; - } - - public void setUsage(double usage) { - this.usage = usage; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java deleted file mode 100644 index 2db7086..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/KV.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xkcoding.websocket.payload; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - *

    - * 键值匹配 - *

    - * - * @package: com.xkcoding.websocket.payload - * @description: 键值匹配 - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:41 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class KV { - /** - * 键 - */ - private String key; - - /** - * 值 - */ - private Object value; -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java deleted file mode 100644 index d36b38a..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/ServerVO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.websocket.payload; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.Server; -import com.xkcoding.websocket.payload.server.*; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 服务器信息VO - *

    - * - * @package: com.xkcoding.websocket.payload - * @description: 服务器信息VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:25 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class ServerVO { - List cpu = Lists.newArrayList(); - List jvm = Lists.newArrayList(); - List mem = Lists.newArrayList(); - List sysFile = Lists.newArrayList(); - List sys = Lists.newArrayList(); - - public ServerVO create(Server server) { - cpu.add(CpuVO.create(server.getCpu())); - jvm.add(JvmVO.create(server.getJvm())); - mem.add(MemVO.create(server.getMem())); - sysFile.add(SysFileVO.create(server.getSysFiles())); - sys.add(SysVO.create(server.getSys())); - return null; - } -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java deleted file mode 100644 index e9524c9..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/CpuVO.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.xkcoding.websocket.payload.server; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.server.Cpu; -import com.xkcoding.websocket.payload.KV; -import lombok.Data; - -import java.util.List; - -/** - *

    - * CPU相关信息实体VO - *

    - * - * @package: com.xkcoding.websocket.payload.server - * @description: CPU相关信息实体VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:27 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class CpuVO { - List data = Lists.newArrayList(); - - public static CpuVO create(Cpu cpu) { - CpuVO vo = new CpuVO(); - vo.data.add(new KV("核心数", cpu.getCpuNum())); - vo.data.add(new KV("CPU总的使用率", cpu.getTotal())); - vo.data.add(new KV("CPU系统使用率", cpu.getSys() + "%")); - vo.data.add(new KV("CPU用户使用率", cpu.getUsed() + "%")); - vo.data.add(new KV("CPU当前等待率", cpu.getWait() + "%")); - vo.data.add(new KV("CPU当前空闲率", cpu.getFree() + "%")); - return vo; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java deleted file mode 100644 index 78b6749..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/JvmVO.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.xkcoding.websocket.payload.server; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.server.Jvm; -import com.xkcoding.websocket.payload.KV; -import lombok.Data; - -import java.util.List; - -/** - *

    - * JVM相关信息实体VO - *

    - * - * @package: com.xkcoding.websocket.payload.server - * @description: JVM相关信息实体VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class JvmVO { - List data = Lists.newArrayList(); - - public static JvmVO create(Jvm jvm) { - JvmVO vo = new JvmVO(); - vo.data.add(new KV("当前JVM占用的内存总数(M)", jvm.getTotal() + "M")); - vo.data.add(new KV("JVM最大可用内存总数(M)", jvm.getMax() + "M")); - vo.data.add(new KV("JVM空闲内存(M)", jvm.getFree() + "M")); - vo.data.add(new KV("JVM使用率", jvm.getUsage() + "%")); - vo.data.add(new KV("JDK版本", jvm.getVersion())); - vo.data.add(new KV("JDK路径", jvm.getHome())); - vo.data.add(new KV("JDK启动时间", jvm.getStartTime())); - vo.data.add(new KV("JDK运行时间", jvm.getRunTime())); - return vo; - } - -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java deleted file mode 100644 index fa24fce..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/MemVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.xkcoding.websocket.payload.server; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.server.Mem; -import com.xkcoding.websocket.payload.KV; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 內存相关信息实体VO - *

    - * - * @package: com.xkcoding.websocket.payload.server - * @description: 內存相关信息实体VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class MemVO { - List data = Lists.newArrayList(); - - public static MemVO create(Mem mem) { - MemVO vo = new MemVO(); - vo.data.add(new KV("内存总量", mem.getTotal() + "G")); - vo.data.add(new KV("已用内存", mem.getUsed() + "G")); - vo.data.add(new KV("剩余内存", mem.getFree() + "G")); - vo.data.add(new KV("使用率", mem.getUsage() + "%")); - return vo; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java deleted file mode 100644 index c4e5e46..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysFileVO.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.xkcoding.websocket.payload.server; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.server.SysFile; -import com.xkcoding.websocket.payload.KV; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 系统文件相关信息实体VO - *

    - * - * @package: com.xkcoding.websocket.payload.server - * @description: 系统文件相关信息实体VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:30 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class SysFileVO { - List> data = Lists.newArrayList(); - - public static SysFileVO create(List sysFiles) { - SysFileVO vo = new SysFileVO(); - for (SysFile sysFile : sysFiles) { - List item = Lists.newArrayList(); - - item.add(new KV("盘符路径", sysFile.getDirName())); - item.add(new KV("盘符类型", sysFile.getSysTypeName())); - item.add(new KV("文件类型", sysFile.getTypeName())); - item.add(new KV("总大小", sysFile.getTotal())); - item.add(new KV("剩余大小", sysFile.getFree())); - item.add(new KV("已经使用量", sysFile.getUsed())); - item.add(new KV("资源的使用率", sysFile.getUsage() + "%")); - - vo.data.add(item); - } - return vo; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java deleted file mode 100644 index 6b722db..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/payload/server/SysVO.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.websocket.payload.server; - -import com.google.common.collect.Lists; -import com.xkcoding.websocket.model.server.Sys; -import com.xkcoding.websocket.payload.KV; -import lombok.Data; - -import java.util.List; - -/** - *

    - * 系统相关信息实体VO - *

    - * - * @package: com.xkcoding.websocket.payload.server - * @description: 系统相关信息实体VO - * @author: yangkai.shen - * @date: Created in 2018-12-14 17:28 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -public class SysVO { - List data = Lists.newArrayList(); - - public static SysVO create(Sys sys) { - SysVO vo = new SysVO(); - vo.data.add(new KV("服务器名称", sys.getComputerName())); - vo.data.add(new KV("服务器Ip", sys.getComputerIp())); - vo.data.add(new KV("项目路径", sys.getUserDir())); - vo.data.add(new KV("操作系统", sys.getOsName())); - vo.data.add(new KV("系统架构", sys.getOsArch())); - return vo; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java deleted file mode 100644 index 62d24e9..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/task/ServerTask.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.xkcoding.websocket.task; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Dict; -import cn.hutool.json.JSONUtil; -import com.xkcoding.websocket.common.WebSocketConsts; -import com.xkcoding.websocket.model.Server; -import com.xkcoding.websocket.payload.ServerVO; -import com.xkcoding.websocket.util.ServerUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.Date; - -/** - *

    - * 服务器定时推送任务 - *

    - * - * @package: com.xkcoding.websocket.task - * @description: 服务器定时推送任务 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:04 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Slf4j -@Component -public class ServerTask { - @Autowired - private SimpMessagingTemplate wsTemplate; - - /** - * 按照标准时间来算,每隔 2s 执行一次 - */ - @Scheduled(cron = "0/2 * * * * ?") - public void websocket() throws Exception { - log.info("【推送消息】开始执行:{}", DateUtil.formatDateTime(new Date())); - // 查询服务器状态 - Server server = new Server(); - server.copyTo(); - ServerVO serverVO = ServerUtil.wrapServerVO(server); - Dict dict = ServerUtil.wrapServerDict(serverVO); - wsTemplate.convertAndSend(WebSocketConsts.PUSH_SERVER, JSONUtil.toJsonStr(dict)); - log.info("【推送消息】执行结束:{}", DateUtil.formatDateTime(new Date())); - } -} diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java deleted file mode 100644 index 68ac0c5..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/IpUtil.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.xkcoding.websocket.util; - -import javax.servlet.http.HttpServletRequest; -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - *

    - * IP 工具类 - *

    - * - * @package: com.xkcoding.websocket.util - * @description: IP 工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-14 16:08 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class IpUtil { - public static String getIpAddr(HttpServletRequest request) { - if (request == null) { - return "unknown"; - } - String ip = request.getHeader("x-forwarded-for"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("X-Forwarded-For"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("X-Real-IP"); - } - - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - - return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; - } - - public static boolean internalIp(String ip) { - byte[] addr = textToNumericFormatV4(ip); - return internalIp(addr) || "127.0.0.1".equals(ip); - } - - private static boolean internalIp(byte[] addr) { - final byte b0 = addr[0]; - final byte b1 = addr[1]; - // 10.x.x.x/8 - final byte SECTION_1 = 0x0A; - // 172.16.x.x/12 - final byte SECTION_2 = (byte) 0xAC; - final byte SECTION_3 = (byte) 0x10; - final byte SECTION_4 = (byte) 0x1F; - // 192.168.x.x/16 - final byte SECTION_5 = (byte) 0xC0; - final byte SECTION_6 = (byte) 0xA8; - switch (b0) { - case SECTION_1: - return true; - case SECTION_2: - if (b1 >= SECTION_3 && b1 <= SECTION_4) { - return true; - } - case SECTION_5: - switch (b1) { - case SECTION_6: - return true; - } - default: - return false; - } - } - - /** - * 将IPv4地址转换成字节 - * - * @param text IPv4地址 - * @return byte 字节 - */ - public static byte[] textToNumericFormatV4(String text) { - if (text.length() == 0) { - return null; - } - - byte[] bytes = new byte[4]; - String[] elements = text.split("\\.", -1); - try { - long l; - int i; - switch (elements.length) { - case 1: - l = Long.parseLong(elements[0]); - if ((l < 0L) || (l > 4294967295L)) { - return null; - } - bytes[0] = (byte) (int) (l >> 24 & 0xFF); - bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); - bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 2: - l = Integer.parseInt(elements[0]); - if ((l < 0L) || (l > 255L)) { - return null; - } - bytes[0] = (byte) (int) (l & 0xFF); - l = Integer.parseInt(elements[1]); - if ((l < 0L) || (l > 16777215L)) { - return null; - } - bytes[1] = (byte) (int) (l >> 16 & 0xFF); - bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 3: - for (i = 0; i < 2; ++i) { - l = Integer.parseInt(elements[i]); - if ((l < 0L) || (l > 255L)) { - return null; - } - bytes[i] = (byte) (int) (l & 0xFF); - } - l = Integer.parseInt(elements[2]); - if ((l < 0L) || (l > 65535L)) { - return null; - } - bytes[2] = (byte) (int) (l >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 4: - for (i = 0; i < 4; ++i) { - l = Integer.parseInt(elements[i]); - if ((l < 0L) || (l > 255L)) { - return null; - } - bytes[i] = (byte) (int) (l & 0xFF); - } - break; - default: - return null; - } - } catch (NumberFormatException e) { - return null; - } - return bytes; - } - - public static String getHostIp() { - try { - return InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - } - return "127.0.0.1"; - } - - public static String getHostName() { - try { - return InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - } - return "未知"; - } -} \ No newline at end of file diff --git a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java b/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java deleted file mode 100644 index 4256f18..0000000 --- a/spring-boot-demo-websocket/src/main/java/com/xkcoding/websocket/util/ServerUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xkcoding.websocket.util; - -import cn.hutool.core.lang.Dict; -import com.xkcoding.websocket.model.Server; -import com.xkcoding.websocket.payload.ServerVO; - -/** - *

    - * 服务器转换工具类 - *

    - * - * @package: com.xkcoding.websocket.util - * @description: 服务器转换工具类 - * @author: yangkai.shen - * @date: Created in 2018-12-17 10:24 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -public class ServerUtil { - /** - * 包装成 ServerVO - * - * @param server server - * @return ServerVO - */ - public static ServerVO wrapServerVO(Server server) { - ServerVO serverVO = new ServerVO(); - serverVO.create(server); - return serverVO; - } - - /** - * 包装成 Dict - * - * @param serverVO serverVO - * @return Dict - */ - public static Dict wrapServerDict(ServerVO serverVO) { - Dict dict = Dict.create() - .set("cpu", serverVO.getCpu().get(0).getData()) - .set("mem", serverVO.getMem().get(0).getData()) - .set("sys", serverVO.getSys().get(0).getData()) - .set("jvm", serverVO.getJvm().get(0).getData()) - .set("sysFile", serverVO.getSysFile().get(0).getData()); - return dict; - } -} diff --git a/spring-boot-demo-zookeeper/README.md b/spring-boot-demo-zookeeper/README.md deleted file mode 100644 index 8b12cf1..0000000 --- a/spring-boot-demo-zookeeper/README.md +++ /dev/null @@ -1,461 +0,0 @@ -# spring-boot-demo-zookeeper - -> 此 demo 主要演示了如何使用 Spring Boot 集成 Zookeeper 结合AOP实现分布式锁。 - -## pom.xml - -```xml - - - 4.0.0 - - spring-boot-demo-zookeeper - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-zookeeper - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-aop - - - - - - org.apache.curator - curator-recipes - 4.1.0 - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-zookeeper - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` - -## ZkProps.java - -```java -/** - *

    - * Zookeeper 配置项 - *

    - * - * @package: com.xkcoding.zookeeper.config.props - * @description: Zookeeper 配置项 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:47 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@ConfigurationProperties(prefix = "zk") -public class ZkProps { - /** - * 连接地址 - */ - private String url; - - /** - * 超时时间(毫秒),默认1000 - */ - private int timeout = 1000; - - /** - * 重试次数,默认3 - */ - private int retry = 3; -} -``` - -## application.yml - -```yaml -server: - port: 8080 - servlet: - context-path: /demo - -zk: - url: 127.0.0.1:2181 - timeout: 1000 - retry: 3 -``` - -## ZkConfig.java - -```java -/** - *

    - * Zookeeper配置类 - *

    - * - * @package: com.xkcoding.zookeeper.config - * @description: Zookeeper配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties(ZkProps.class) -public class ZkConfig { - private final ZkProps zkProps; - - @Autowired - public ZkConfig(ZkProps zkProps) { - this.zkProps = zkProps; - } - - @Bean - public CuratorFramework curatorFramework() { - RetryPolicy retryPolicy = new ExponentialBackoffRetry(zkProps.getTimeout(), zkProps.getRetry()); - CuratorFramework client = CuratorFrameworkFactory.newClient(zkProps.getUrl(), retryPolicy); - client.start(); - return client; - } -} -``` - -## ZooLock.java - -> 分布式锁的关键注解 - -```java -/** - *

    - * 基于Zookeeper的分布式锁注解 - * 在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 - *

    - * - * @package: com.xkcoding.zookeeper.annotation - * @description: 基于Zookeeper的分布式锁注解,在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:11 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -public @interface ZooLock { - /** - * 分布式锁的键 - */ - String key(); - - /** - * 锁释放时间,默认五秒 - */ - long timeout() default 5 * 1000; - - /** - * 时间格式,默认:毫秒 - */ - TimeUnit timeUnit() default TimeUnit.MILLISECONDS; -} -``` - -## LockKeyParam.java - -> 分布式锁动态key的关键注解 - -```java -/** - *

    - * 分布式锁动态key注解,配置之后key的值会动态获取参数内容 - *

    - * - * @package: com.xkcoding.zookeeper.annotation - * @description: 分布式锁动态key注解,配置之后key的值会动态获取参数内容 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Target({ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -public @interface LockKeyParam { - /** - * 如果动态key在user对象中,那么就需要设置fields的值为user对象中的属性名可以为多个,基本类型则不需要设置该值 - *

    例1:public void count(@LockKeyParam({"id"}) User user) - *

    例2:public void count(@LockKeyParam({"id","userName"}) User user) - *

    例3:public void count(@LockKeyParam String userId) - */ - String[] fields() default {}; -} -``` - -## ZooLockAspect.java - -> 分布式锁的关键部分 - -```java -/** - *

    - * 使用 aop 切面记录请求日志信息 - *

    - * - * @package: com.xkcoding.log.aop.aspectj - * @description: 使用 aop 切面记录请求日志信息 - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:05 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Aspect -@Component -@Slf4j -public class ZooLockAspect { - private final CuratorFramework zkClient; - - private static final String KEY_PREFIX = "DISTRIBUTED_LOCK_"; - - private static final String KEY_SEPARATOR = "/"; - - @Autowired - public ZooLockAspect(CuratorFramework zkClient) { - this.zkClient = zkClient; - } - - /** - * 切入点 - */ - @Pointcut("@annotation(com.xkcoding.zookeeper.annotation.ZooLock)") - public void doLock() { - - } - - /** - * 环绕操作 - * - * @param point 切入点 - * @return 原方法返回值 - * @throws Throwable 异常信息 - */ - @Around("doLock()") - public Object around(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - Object[] args = point.getArgs(); - ZooLock zooLock = method.getAnnotation(ZooLock.class); - if (StrUtil.isBlank(zooLock.key())) { - throw new RuntimeException("分布式锁键不能为空"); - } - String lockKey = buildLockKey(zooLock, method, args); - InterProcessMutex lock = new InterProcessMutex(zkClient, lockKey); - try { - // 假设上锁成功,以后拿到的都是 false - if (lock.acquire(zooLock.timeout(), zooLock.timeUnit())) { - return point.proceed(); - } else { - throw new RuntimeException("请勿重复提交"); - } - } finally { - lock.release(); - } - } - - /** - * 构造分布式锁的键 - * - * @param lock 注解 - * @param method 注解标记的方法 - * @param args 方法上的参数 - * @return - * @throws NoSuchFieldException - * @throws IllegalAccessException - */ - private String buildLockKey(ZooLock lock, Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { - StringBuilder key = new StringBuilder(KEY_SEPARATOR + KEY_PREFIX + lock.key()); - - // 迭代全部参数的注解,根据使用LockKeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上 - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - - for (int i = 0; i < parameterAnnotations.length; i++) { - // 循环该参数全部注解 - for (Annotation annotation : parameterAnnotations[i]) { - // 注解不是 @LockKeyParam - if (!annotation.annotationType().isInstance(LockKeyParam.class)) { - continue; - } - - // 获取所有fields - String[] fields = ((LockKeyParam) annotation).fields(); - if (ArrayUtil.isEmpty(fields)) { - // 普通数据类型直接拼接 - if (ObjectUtil.isNull(args[i])) { - throw new RuntimeException("动态参数不能为null"); - } - key.append(KEY_SEPARATOR).append(args[i]); - } else { - // @LockKeyParam的fields值不为null,所以当前参数应该是对象类型 - for (String field : fields) { - Class clazz = args[i].getClass(); - Field declaredField = clazz.getDeclaredField(field); - declaredField.setAccessible(true); - Object value = declaredField.get(clazz); - key.append(KEY_SEPARATOR).append(value); - } - } - } - } - return key.toString(); - } - -} -``` - -## SpringBootDemoZookeeperApplicationTests.java - -> 测试分布式锁 - -```java -@RunWith(SpringRunner.class) -@SpringBootTest -@Slf4j -public class SpringBootDemoZookeeperApplicationTests { - - public Integer getCount() { - return count; - } - - private Integer count = 10000; - private ExecutorService executorService = Executors.newFixedThreadPool(1000); - - @Autowired - private CuratorFramework zkClient; - - /** - * 不使用分布式锁,程序结束查看count的值是否为0 - */ - @Test - public void test() throws InterruptedException { - IntStream.range(0, 10000).forEach(i -> executorService.execute(this::doBuy)); - TimeUnit.MINUTES.sleep(1); - log.error("count值为{}", count); - } - - /** - * 测试AOP分布式锁 - */ - @Test - public void testAopLock() throws InterruptedException { - // 测试类中使用AOP需要手动代理 - SpringBootDemoZookeeperApplicationTests target = new SpringBootDemoZookeeperApplicationTests(); - AspectJProxyFactory factory = new AspectJProxyFactory(target); - ZooLockAspect aspect = new ZooLockAspect(zkClient); - factory.addAspect(aspect); - SpringBootDemoZookeeperApplicationTests proxy = factory.getProxy(); - IntStream.range(0, 10000).forEach(i -> executorService.execute(() -> proxy.aopBuy(i))); - TimeUnit.MINUTES.sleep(1); - log.error("count值为{}", proxy.getCount()); - } - - /** - * 测试手动加锁 - */ - @Test - public void testManualLock() throws InterruptedException { - IntStream.range(0, 10000).forEach(i -> executorService.execute(this::manualBuy)); - TimeUnit.MINUTES.sleep(1); - log.error("count值为{}", count); - } - - @ZooLock(key = "buy", timeout = 1, timeUnit = TimeUnit.MINUTES) - public void aopBuy(int userId) { - log.info("{} 正在出库。。。", userId); - doBuy(); - log.info("{} 扣库存成功。。。", userId); - } - - public void manualBuy() { - String lockPath = "/buy"; - log.info("try to buy sth."); - try { - InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath); - try { - if (lock.acquire(1, TimeUnit.MINUTES)) { - doBuy(); - log.info("buy successfully!"); - } - } finally { - lock.release(); - } - } catch (Exception e) { - log.error("zk error"); - } - } - - public void doBuy() { - count--; - log.info("count值为{}", count); - } - -} -``` - -## 参考 - -1. [如何在测试类中使用 AOP](https://stackoverflow.com/questions/11436600/unit-testing-spring-around-aop-methods) -2. zookeeper 实现分布式锁:《Spring Boot 2精髓 从构建小系统到架构分布式大系统》李家智 - 第16章 - Spring Boot 和 Zoo Keeper - 16.3 实现分布式锁 \ No newline at end of file diff --git a/spring-boot-demo-zookeeper/pom.xml b/spring-boot-demo-zookeeper/pom.xml deleted file mode 100644 index b3fdb99..0000000 --- a/spring-boot-demo-zookeeper/pom.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - 4.0.0 - - spring-boot-demo-zookeeper - 1.0.0-SNAPSHOT - jar - - spring-boot-demo-zookeeper - Demo project for Spring Boot - - - com.xkcoding - spring-boot-demo - 1.0.0-SNAPSHOT - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-aop - - - - - - org.apache.curator - curator-recipes - 4.1.0 - - - - cn.hutool - hutool-all - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - spring-boot-demo-zookeeper - - - org.springframework.boot - spring-boot-maven-plugin - - - - - \ No newline at end of file diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java deleted file mode 100644 index 24867cf..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/SpringBootDemoZookeeperApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xkcoding.zookeeper; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - *

    - * 启动器 - *

    - * - * @package: com.xkcoding.zookeeper - * @description: 启动器 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:51 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@SpringBootApplication -public class SpringBootDemoZookeeperApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringBootDemoZookeeperApplication.class, args); - } - -} - diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java deleted file mode 100644 index c010e6b..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/LockKeyParam.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xkcoding.zookeeper.annotation; - -import java.lang.annotation.*; - -/** - *

    - * 分布式锁动态key注解,配置之后key的值会动态获取参数内容 - *

    - * - * @package: com.xkcoding.zookeeper.annotation - * @description: 分布式锁动态key注解,配置之后key的值会动态获取参数内容 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:17 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Target({ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -public @interface LockKeyParam { - /** - * 如果动态key在user对象中,那么就需要设置fields的值为user对象中的属性名可以为多个,基本类型则不需要设置该值 - *

    例1:public void count(@LockKeyParam({"id"}) User user) - *

    例2:public void count(@LockKeyParam({"id","userName"}) User user) - *

    例3:public void count(@LockKeyParam String userId) - */ - String[] fields() default {}; -} diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java deleted file mode 100644 index 6be1120..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/annotation/ZooLock.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xkcoding.zookeeper.annotation; - -import java.lang.annotation.*; -import java.util.concurrent.TimeUnit; - -/** - *

    - * 基于Zookeeper的分布式锁注解 - * 在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 - *

    - * - * @package: com.xkcoding.zookeeper.annotation - * @description: 基于Zookeeper的分布式锁注解,在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:11 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -public @interface ZooLock { - /** - * 分布式锁的键 - */ - String key(); - - /** - * 锁释放时间,默认五秒 - */ - long timeout() default 5 * 1000; - - /** - * 时间格式,默认:毫秒 - */ - TimeUnit timeUnit() default TimeUnit.MILLISECONDS; -} diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java deleted file mode 100644 index de1bb3d..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/aspectj/ZooLockAspect.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.xkcoding.zookeeper.aspectj; - -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.xkcoding.zookeeper.annotation.LockKeyParam; -import com.xkcoding.zookeeper.annotation.ZooLock; -import lombok.extern.slf4j.Slf4j; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.recipes.locks.InterProcessMutex; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - *

    - * 使用 aop 切面记录请求日志信息 - *

    - * - * @package: com.xkcoding.log.aop.aspectj - * @description: 使用 aop 切面记录请求日志信息 - * @author: yangkai.shen - * @date: Created in 2018/10/1 10:05 PM - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Aspect -@Component -@Slf4j -public class ZooLockAspect { - private final CuratorFramework zkClient; - - private static final String KEY_PREFIX = "DISTRIBUTED_LOCK_"; - - private static final String KEY_SEPARATOR = "/"; - - @Autowired - public ZooLockAspect(CuratorFramework zkClient) { - this.zkClient = zkClient; - } - - /** - * 切入点 - */ - @Pointcut("@annotation(com.xkcoding.zookeeper.annotation.ZooLock)") - public void doLock() { - - } - - /** - * 环绕操作 - * - * @param point 切入点 - * @return 原方法返回值 - * @throws Throwable 异常信息 - */ - @Around("doLock()") - public Object around(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - Method method = signature.getMethod(); - Object[] args = point.getArgs(); - ZooLock zooLock = method.getAnnotation(ZooLock.class); - if (StrUtil.isBlank(zooLock.key())) { - throw new RuntimeException("分布式锁键不能为空"); - } - String lockKey = buildLockKey(zooLock, method, args); - InterProcessMutex lock = new InterProcessMutex(zkClient, lockKey); - try { - // 假设上锁成功,以后拿到的都是 false - if (lock.acquire(zooLock.timeout(), zooLock.timeUnit())) { - return point.proceed(); - } else { - throw new RuntimeException("请勿重复提交"); - } - } finally { - lock.release(); - } - } - - /** - * 构造分布式锁的键 - * - * @param lock 注解 - * @param method 注解标记的方法 - * @param args 方法上的参数 - * @return - * @throws NoSuchFieldException - * @throws IllegalAccessException - */ - private String buildLockKey(ZooLock lock, Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { - StringBuilder key = new StringBuilder(KEY_SEPARATOR + KEY_PREFIX + lock.key()); - - // 迭代全部参数的注解,根据使用LockKeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上 - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - - for (int i = 0; i < parameterAnnotations.length; i++) { - // 循环该参数全部注解 - for (Annotation annotation : parameterAnnotations[i]) { - // 注解不是 @LockKeyParam - if (!annotation.annotationType().isInstance(LockKeyParam.class)) { - continue; - } - - // 获取所有fields - String[] fields = ((LockKeyParam) annotation).fields(); - if (ArrayUtil.isEmpty(fields)) { - // 普通数据类型直接拼接 - if (ObjectUtil.isNull(args[i])) { - throw new RuntimeException("动态参数不能为null"); - } - key.append(KEY_SEPARATOR).append(args[i]); - } else { - // @LockKeyParam的fields值不为null,所以当前参数应该是对象类型 - for (String field : fields) { - Class clazz = args[i].getClass(); - Field declaredField = clazz.getDeclaredField(field); - declaredField.setAccessible(true); - Object value = declaredField.get(clazz); - key.append(KEY_SEPARATOR).append(value); - } - } - } - } - return key.toString(); - } - -} diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java deleted file mode 100644 index 5f5a656..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/ZkConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.xkcoding.zookeeper.config; - -import com.xkcoding.zookeeper.config.props.ZkProps; -import org.apache.curator.RetryPolicy; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.retry.ExponentialBackoffRetry; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - *

    - * Zookeeper配置类 - *

    - * - * @package: com.xkcoding.zookeeper.config - * @description: Zookeeper配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:45 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Configuration -@EnableConfigurationProperties(ZkProps.class) -public class ZkConfig { - private final ZkProps zkProps; - - @Autowired - public ZkConfig(ZkProps zkProps) { - this.zkProps = zkProps; - } - - @Bean - public CuratorFramework curatorFramework() { - RetryPolicy retryPolicy = new ExponentialBackoffRetry(zkProps.getTimeout(), zkProps.getRetry()); - CuratorFramework client = CuratorFrameworkFactory.newClient(zkProps.getUrl(), retryPolicy); - client.start(); - return client; - } -} diff --git a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java b/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java deleted file mode 100644 index a944fb7..0000000 --- a/spring-boot-demo-zookeeper/src/main/java/com/xkcoding/zookeeper/config/props/ZkProps.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xkcoding.zookeeper.config.props; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - *

    - * Zookeeper 配置项 - *

    - * - * @package: com.xkcoding.zookeeper.config.props - * @description: Zookeeper 配置项 - * @author: yangkai.shen - * @date: Created in 2018-12-27 14:47 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen - */ -@Data -@ConfigurationProperties(prefix = "zk") -public class ZkProps { - /** - * 连接地址 - */ - private String url; - - /** - * 超时时间(毫秒),默认1000 - */ - private int timeout = 1000; - - /** - * 重试次数,默认3 - */ - private int retry = 3; -}