diff --git a/.gitignore b/.gitignore index a1c2a23..c0d39de 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ *.zip *.tar.gz *.rar +*.iml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +.idea +/.idea/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..100bbba --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + com.mangmang + learning-nexus + 1.0.0 + pom + learning-nexus + + + + reactive-programming + spring-data-jpa-read-write-separation + spring-cloud-demo + swing-and-javafx + websocket + + + + 17 + 2.6.7 + 2021.0.2 + ${java.version} + ${java.version} + UTF-8 + 0.16.0 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + + diff --git a/reactive-programming/pom.xml b/reactive-programming/pom.xml new file mode 100644 index 0000000..bad5628 --- /dev/null +++ b/reactive-programming/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.mangmang + reactive-programming + 1.0-SNAPSHOT + jar + + + com.mangmang + learning-nexus + 1.0.0 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + org.projectlombok + lombok + true + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/reactive-programming/src/main/java/org/mangmang/ReactiveProgrammingApplication.java b/reactive-programming/src/main/java/org/mangmang/ReactiveProgrammingApplication.java new file mode 100644 index 0000000..7289d0d --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/ReactiveProgrammingApplication.java @@ -0,0 +1,15 @@ +package org.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Hello world! + * + */ +@SpringBootApplication +public class ReactiveProgrammingApplication { + public static void main(String[] args) { + SpringApplication.run(ReactiveProgrammingApplication.class, args); + } +} diff --git a/reactive-programming/src/main/java/org/mangmang/controller/HelloController.java b/reactive-programming/src/main/java/org/mangmang/controller/HelloController.java new file mode 100644 index 0000000..9fd6d20 --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/controller/HelloController.java @@ -0,0 +1,12 @@ +package org.mangmang.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @GetMapping("/hello") + public String sayHello() { + return "Hello, Spring Boot!"; + } +} diff --git a/reactive-programming/src/main/java/org/mangmang/controller/ProductController.java b/reactive-programming/src/main/java/org/mangmang/controller/ProductController.java new file mode 100644 index 0000000..ca3b33d --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/controller/ProductController.java @@ -0,0 +1,74 @@ +package org.mangmang.controller; + +import org.mangmang.entity.Product; +import org.mangmang.service.ProductService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/products") +public class ProductController { + + private final ProductService productService; + + @Autowired + public ProductController(ProductService productService) { + this.productService = productService; + } + + @GetMapping + public Flux getAllProducts() { + return productService.getAllProducts(); + } + + @GetMapping("/{id}") + public Mono> getProductById(@PathVariable String id) { + return productService.getProductById(id) + .map(ResponseEntity::ok) + .defaultIfEmpty(ResponseEntity.notFound().build()); + } + + @GetMapping("/category/{category}") + public Flux getProductsByCategory(@PathVariable String category) { + return productService.getProductsByCategory(category); + } + + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux streamAllProducts() { + return productService.getAllProducts(); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Mono createProduct(@RequestBody Product product) { + return productService.saveProduct(product); + } + + @PutMapping("/{id}") + public Mono> updateProduct(@PathVariable String id, @RequestBody Product product) { + return productService.getProductById(id) + .flatMap(existingProduct -> { + existingProduct.setName(product.getName()); + existingProduct.setPrice(product.getPrice()); + existingProduct.setCategory(product.getCategory()); + return productService.saveProduct(existingProduct); + }) + .map(updatedProduct -> ResponseEntity.ok(updatedProduct)) + .defaultIfEmpty(ResponseEntity.notFound().build()); + } + + @DeleteMapping("/{id}") + public Mono> deleteProduct(@PathVariable String id) { + return productService.getProductById(id) + .flatMap(existingProduct -> + productService.deleteProduct(id) + .then(Mono.just(new ResponseEntity(HttpStatus.OK))) + ) + .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + } +} diff --git a/reactive-programming/src/main/java/org/mangmang/entity/Product.java b/reactive-programming/src/main/java/org/mangmang/entity/Product.java new file mode 100644 index 0000000..4f552db --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/entity/Product.java @@ -0,0 +1,15 @@ +package org.mangmang.entity; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@Document +public class Product { + @Id + private String id; + private String name; + private double price; + private String category; +} diff --git a/reactive-programming/src/main/java/org/mangmang/repository/ProductRepository.java b/reactive-programming/src/main/java/org/mangmang/repository/ProductRepository.java new file mode 100644 index 0000000..16360df --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/repository/ProductRepository.java @@ -0,0 +1,9 @@ +package org.mangmang.repository; + +import org.mangmang.entity.Product; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import reactor.core.publisher.Flux; + +public interface ProductRepository extends ReactiveMongoRepository { + Flux findByCategory(String category); +} diff --git a/reactive-programming/src/main/java/org/mangmang/service/ProductService.java b/reactive-programming/src/main/java/org/mangmang/service/ProductService.java new file mode 100644 index 0000000..e17960d --- /dev/null +++ b/reactive-programming/src/main/java/org/mangmang/service/ProductService.java @@ -0,0 +1,39 @@ +package org.mangmang.service; + +import org.mangmang.entity.Product; +import org.mangmang.repository.ProductRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Service +public class ProductService { + + private final ProductRepository productRepository; + + @Autowired + public ProductService(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + public Flux getAllProducts() { + return productRepository.findAll(); + } + + public Mono getProductById(String id) { + return productRepository.findById(id); + } + + public Flux getProductsByCategory(String category) { + return productRepository.findByCategory(category); + } + + public Mono saveProduct(Product product) { + return productRepository.save(product); + } + + public Mono deleteProduct(String id) { + return productRepository.deleteById(id); + } +} diff --git a/reactive-programming/src/main/resources/application.yml b/reactive-programming/src/main/resources/application.yml new file mode 100644 index 0000000..e69de29 diff --git a/spring-cloud-demo/config-server/pom.xml b/spring-cloud-demo/config-server/pom.xml new file mode 100644 index 0000000..8225fe6 --- /dev/null +++ b/spring-cloud-demo/config-server/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.mangmang + spring-cloud-demo + 1.0.0 + + + + config-server + config-server + jar + + + + org.springframework.cloud + spring-cloud-config-server + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud-demo/config-server/src/main/java/com/mangmang/ConfigServerApplication.java b/spring-cloud-demo/config-server/src/main/java/com/mangmang/ConfigServerApplication.java new file mode 100644 index 0000000..22d4161 --- /dev/null +++ b/spring-cloud-demo/config-server/src/main/java/com/mangmang/ConfigServerApplication.java @@ -0,0 +1,19 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.config.server.EnableConfigServer; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +/** + * 配置中心 + * 功能:统一管理各服务的配置,支持从 Git 仓库中读取配置文件 + */ +@SpringBootApplication +@EnableConfigServer +@EnableEurekaClient +public class ConfigServerApplication { + public static void main(String[] args) { + SpringApplication.run(ConfigServerApplication.class, args); + } +} diff --git a/spring-cloud-demo/config-server/src/main/resources/application.yml b/spring-cloud-demo/config-server/src/main/resources/application.yml new file mode 100644 index 0000000..95846fa --- /dev/null +++ b/spring-cloud-demo/config-server/src/main/resources/application.yml @@ -0,0 +1,27 @@ +# 服务器配置 +server: + # 服务器端口号,设置为8888 + port: 8888 + +# Spring应用配置 +spring: + application: + # 应用名称,设置为config-server + name: config-server + cloud: + config: + server: + git: + # Git仓库的URI,用于存储配置文件 + uri: https://github.com/liujing33/MangMang + # 默认的Git分支,设置为main + default-label: main + # 搜索路径,使用{application}作为占位符,表示根据应用名称查找配置文件 + search-paths: / + +# Eureka客户端配置 +eureka: + client: + serviceUrl: + # Eureka服务器的默认区域URL,设置为本地8761端口 + defaultZone: http://localhost:8761/eureka/ diff --git a/spring-cloud-demo/eureka-server/pom.xml b/spring-cloud-demo/eureka-server/pom.xml new file mode 100644 index 0000000..a276d2c --- /dev/null +++ b/spring-cloud-demo/eureka-server/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + com.mangmang + spring-cloud-demo + 1.0.0 + + + eureka-server + jar + eureka-server + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud-demo/eureka-server/src/main/java/com/mangmang/EurekaServerApplication.java b/spring-cloud-demo/eureka-server/src/main/java/com/mangmang/EurekaServerApplication.java new file mode 100644 index 0000000..b76934b --- /dev/null +++ b/spring-cloud-demo/eureka-server/src/main/java/com/mangmang/EurekaServerApplication.java @@ -0,0 +1,17 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + +/** + * 服务注册中心 + * 功能:用于服务注册和发现,微服务启动时会向 Eureka 注册自己,并通过 Eureka 发现其他服务 + */ +@SpringBootApplication +@EnableEurekaServer +public class EurekaServerApplication { + public static void main(String[] args) { + SpringApplication.run(EurekaServerApplication.class, args); + } +} diff --git a/spring-cloud-demo/eureka-server/src/main/resources/application.yml b/spring-cloud-demo/eureka-server/src/main/resources/application.yml new file mode 100644 index 0000000..85da4fa --- /dev/null +++ b/spring-cloud-demo/eureka-server/src/main/resources/application.yml @@ -0,0 +1,21 @@ +# server: 配置服务器相关的参数 +server: + # port: 指定服务器监听的端口号,默认值为8761 + port: 8761 + +# spring: 配置Spring应用相关的参数 +spring: + application: + # name: 指定Spring应用的名称,这里设置为eureka-server + name: eureka-server + +# eureka: 配置Eureka服务器和客户端相关的参数 +eureka: + client: + # register-with-eureka: 是否将当前实例注册到Eureka服务器,false表示不注册 + register-with-eureka: false + # fetch-registry: 是否从Eureka服务器获取注册表信息,false表示不获取 + fetch-registry: false + server: + # wait-time-in-ms-when-sync-empty: 当同步空注册表时的等待时间(毫秒),0表示不等待 + wait-time-in-ms-when-sync-empty: 0 diff --git a/spring-cloud-demo/gateway-service/pom.xml b/spring-cloud-demo/gateway-service/pom.xml new file mode 100644 index 0000000..c827629 --- /dev/null +++ b/spring-cloud-demo/gateway-service/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + com.mangmang + spring-cloud-demo + 1.0.0 + + + gateway-service + gateway-service + jar + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud-demo/gateway-service/src/main/java/com/mangmang/GatewayServiceApplication.java b/spring-cloud-demo/gateway-service/src/main/java/com/mangmang/GatewayServiceApplication.java new file mode 100644 index 0000000..510e2d9 --- /dev/null +++ b/spring-cloud-demo/gateway-service/src/main/java/com/mangmang/GatewayServiceApplication.java @@ -0,0 +1,17 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +/** + * Hello world! + * + */ +@SpringBootApplication +@EnableEurekaClient +public class GatewayServiceApplication { + public static void main( String[] args ) { + SpringApplication.run(GatewayServiceApplication.class, args); + } +} diff --git a/spring-cloud-demo/gateway-service/src/main/resources/application.yml b/spring-cloud-demo/gateway-service/src/main/resources/application.yml new file mode 100644 index 0000000..97a6b83 --- /dev/null +++ b/spring-cloud-demo/gateway-service/src/main/resources/application.yml @@ -0,0 +1,49 @@ +# 服务器配置 +server: + # 服务器端口号,设置为8080 + port: 8080 + +# Eureka客户端配置 +eureka: + client: + serviceUrl: + # Eureka服务器的默认区域URL,指向本地8761端口的Eureka服务 + defaultZone: http://localhost:8761/eureka/ + +# Spring Cloud配置 +spring: + cloud: + gateway: + discovery: + locator: + # 启用服务发现定位器,允许通过服务名称进行路由 + enabled: true + routes: + # 用户服务路由配置 + - id: user-service + # 使用负载均衡方式指向user-service服务 + uri: lb://user-service + predicates: + # 匹配路径为/api/users/**的请求 + - Path=/api/users/** + filters: + # 去除路径中的第一个前缀 + - StripPrefix=1 + # 订单服务路由配置 + - id: order-service + # 使用负载均衡方式指向order-service服务 + uri: lb://order-service + predicates: + # 匹配路径为/api/orders/**的请求 + - Path=/api/orders/** + filters: + # 去除路径中的第一个前缀 + - StripPrefix=1 + +# 管理端点配置 +management: + endpoints: + web: + exposure: + # 暴露所有管理端点 + include: "*" diff --git a/spring-cloud-demo/gateway-service/src/main/resources/bootstrap.yml b/spring-cloud-demo/gateway-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..198cf97 --- /dev/null +++ b/spring-cloud-demo/gateway-service/src/main/resources/bootstrap.yml @@ -0,0 +1,18 @@ +# Spring 应用程序配置 +spring: + # 应用程序相关配置 + application: + # 应用程序名称,用于标识当前服务 + name: gateway-service + # Spring Cloud 相关配置 + cloud: + # 配置中心相关配置 + config: + # 配置中心服务发现相关配置 + discovery: + # 是否启用配置中心服务发现,true 表示启用 + enabled: true + # 配置中心服务的服务ID,用于服务发现 + service-id: config-server + # 是否在配置中心不可用时快速失败,true 表示快速失败 + fail-fast: true diff --git a/spring-cloud-demo/order-service/pom.xml b/spring-cloud-demo/order-service/pom.xml new file mode 100644 index 0000000..59dc54e --- /dev/null +++ b/spring-cloud-demo/order-service/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + com.mangmang + spring-cloud-demo + 1.0.0 + + + order-service + order-service + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud-demo/order-service/src/main/java/com/mangmang/OrderServiceApplication.java b/spring-cloud-demo/order-service/src/main/java/com/mangmang/OrderServiceApplication.java new file mode 100644 index 0000000..35a0995 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/java/com/mangmang/OrderServiceApplication.java @@ -0,0 +1,16 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.cloud.openfeign.EnableFeignClients; + + +@SpringBootApplication +@EnableEurekaClient +@EnableFeignClients +public class OrderServiceApplication { + public static void main(String[] args) { + SpringApplication.run(OrderServiceApplication.class, args); + } +} diff --git a/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/UserClient.java b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/UserClient.java new file mode 100644 index 0000000..031f0fe --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/UserClient.java @@ -0,0 +1,13 @@ +package com.mangmang.orderservice.client; + +import com.mangmang.orderservice.client.model.User; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@FeignClient(name = "user-service") +public interface UserClient { + + @GetMapping("/users/{id}") + User getUserById(@PathVariable("id") Long id); +} diff --git a/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/Order.java b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/Order.java new file mode 100644 index 0000000..32d5043 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/Order.java @@ -0,0 +1,15 @@ +package com.mangmang.orderservice.client.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Order { + private Long id; + private Long userId; + private String productName; + private Double price; +} diff --git a/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/User.java b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/User.java new file mode 100644 index 0000000..28b7b75 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/client/model/User.java @@ -0,0 +1,10 @@ +package com.mangmang.orderservice.client.model; + +import lombok.Data; + +@Data +public class User { + private Long id; + private String name; + private String email; +} diff --git a/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/controller/OrderController.java b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/controller/OrderController.java new file mode 100644 index 0000000..6abbf53 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/java/com/mangmang/orderservice/controller/OrderController.java @@ -0,0 +1,65 @@ +package com.mangmang.orderservice.controller; + +import com.mangmang.orderservice.client.UserClient; +import com.mangmang.orderservice.client.model.Order; +import com.mangmang.orderservice.client.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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 java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/orders") +public class OrderController { + + @Value("${server.port}") + private String port; + + @Autowired + private UserClient userClient; + + private List orders = new ArrayList<>(); + + public OrderController() { + orders.add(new Order(1L, 1L, "iPhone 13", 5999.99)); + orders.add(new Order(2L, 1L, "MacBook Pro", 12999.99)); + orders.add(new Order(3L, 2L, "iPad Pro", 4999.99)); + } + + @GetMapping + public List getAllOrders() { + return orders; + } + + @GetMapping("/{id}") + public Order getOrderById(@PathVariable Long id) { + return orders.stream() + .filter(order -> order.getId().equals(id)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Order not found")); + } + + @GetMapping("/user/{userId}") + public List getOrdersByUserId(@PathVariable Long userId) { + // 验证用户是否存在 + User user = userClient.getUserById(userId); + if (user == null) { + throw new RuntimeException("User not found"); + } + + return orders.stream() + .filter(order -> order.getUserId().equals(userId)) + .collect(Collectors.toList()); + } + + @GetMapping("/port") + public String getPort() { + return "Order service running on port: " + port; + } +} \ No newline at end of file diff --git a/spring-cloud-demo/order-service/src/main/resources/application.yml b/spring-cloud-demo/order-service/src/main/resources/application.yml new file mode 100644 index 0000000..d312ab3 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/resources/application.yml @@ -0,0 +1,19 @@ +# server配置块,用于定义服务器相关的配置 +server: + # 服务器端口号,指定应用程序监听的端口 + port: 8082 + +# eureka配置块,用于定义Eureka客户端相关的配置 +eureka: + client: + serviceUrl: + # Eureka服务器的默认区域URL,指定Eureka服务器的地址 + defaultZone: http://localhost:8761/eureka/ + +# management配置块,用于定义管理端点相关的配置 +management: + endpoints: + web: + exposure: + # 暴露所有管理端点,允许通过Web访问所有管理端点 + include: "*" diff --git a/spring-cloud-demo/order-service/src/main/resources/bootstrap.yml b/spring-cloud-demo/order-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..899d581 --- /dev/null +++ b/spring-cloud-demo/order-service/src/main/resources/bootstrap.yml @@ -0,0 +1,18 @@ +# Spring 应用程序配置 +spring: + # 应用程序相关配置 + application: + # 应用程序名称,用于标识当前服务 + name: order-service + # Spring Cloud 相关配置 + cloud: + # Spring Cloud Config 配置 + config: + # 配置服务发现相关设置 + discovery: + # 启用配置服务发现,允许通过服务发现机制查找配置服务器 + enabled: true + # 配置服务器的服务ID,用于在服务注册中心查找配置服务器 + service-id: config-server + # 配置客户端在启动时快速失败,如果无法连接到配置服务器,则应用程序将无法启动 + fail-fast: true diff --git a/spring-cloud-demo/pom.xml b/spring-cloud-demo/pom.xml new file mode 100644 index 0000000..f249b3b --- /dev/null +++ b/spring-cloud-demo/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + + com.mangmang + learning-nexus + 1.0.0 + + + + spring-cloud-demo + spring-cloud-demo + pom + + + eureka-server + config-server + user-service + order-service + gateway-service + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/spring-cloud-demo/user-service/pom.xml b/spring-cloud-demo/user-service/pom.xml new file mode 100644 index 0000000..d16f128 --- /dev/null +++ b/spring-cloud-demo/user-service/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + + com.mangmang + spring-cloud-demo + 1.0.0 + + + + user-service + user-service + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-cloud-demo/user-service/src/main/java/com/mangmang/UserServiceApplication.java b/spring-cloud-demo/user-service/src/main/java/com/mangmang/UserServiceApplication.java new file mode 100644 index 0000000..05ad540 --- /dev/null +++ b/spring-cloud-demo/user-service/src/main/java/com/mangmang/UserServiceApplication.java @@ -0,0 +1,16 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +/** + * 用户服务 + */ +@SpringBootApplication +@EnableEurekaClient +public class UserServiceApplication { + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } +} diff --git a/spring-cloud-demo/user-service/src/main/java/com/mangmang/controller/UserController.java b/spring-cloud-demo/user-service/src/main/java/com/mangmang/controller/UserController.java new file mode 100644 index 0000000..662928c --- /dev/null +++ b/spring-cloud-demo/user-service/src/main/java/com/mangmang/controller/UserController.java @@ -0,0 +1,43 @@ +package com.mangmang.controller; + +import com.mangmang.entity.User; +import org.springframework.beans.factory.annotation.Value; +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 java.util.Arrays; +import java.util.List; + +@RestController +@RequestMapping("/users") +public class UserController { + + @Value("${server.port}") + private String port; + + private final List users = Arrays.asList( + new User(1L, "张三", "zhangsan@example.com"), + new User(2L, "李四", "lisi@example.com"), + new User(3L, "王五", "wangwu@example.com") + ); + + @GetMapping + public List getAllUsers() { + return users; + } + + @GetMapping("/{id}") + public User getUserById(@PathVariable Long id) { + return users.stream() + .filter(user -> user.getId().equals(id)) + .findFirst() + .orElseThrow(() -> new RuntimeException("User not found")); + } + + @GetMapping("/port") + public String getPort() { + return "User service running on port: " + port; + } +} diff --git a/spring-cloud-demo/user-service/src/main/java/com/mangmang/entity/User.java b/spring-cloud-demo/user-service/src/main/java/com/mangmang/entity/User.java new file mode 100644 index 0000000..51d680c --- /dev/null +++ b/spring-cloud-demo/user-service/src/main/java/com/mangmang/entity/User.java @@ -0,0 +1,14 @@ +package com.mangmang.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + private Long id; + private String name; + private String email; +} diff --git a/spring-cloud-demo/user-service/src/main/resources/application.yml b/spring-cloud-demo/user-service/src/main/resources/application.yml new file mode 100644 index 0000000..04d0bbf --- /dev/null +++ b/spring-cloud-demo/user-service/src/main/resources/application.yml @@ -0,0 +1,19 @@ +# server: 配置服务器相关的设置 +server: + # port: 指定服务器监听的端口号,默认为8081 + port: 8081 + +# eureka: 配置Eureka客户端相关的设置 +eureka: + client: + serviceUrl: + # defaultZone: 指定Eureka服务器的默认注册地址,默认为http://localhost:8761/eureka/ + defaultZone: http://localhost:8761/eureka/ + +# management: 配置Spring Boot Actuator相关的设置 +management: + endpoints: + web: + exposure: + # include: 指定暴露的Actuator端点,*表示暴露所有端点 + include: "*" diff --git a/spring-cloud-demo/user-service/src/main/resources/bootstrap.yml b/spring-cloud-demo/user-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..bd34625 --- /dev/null +++ b/spring-cloud-demo/user-service/src/main/resources/bootstrap.yml @@ -0,0 +1,19 @@ +# Spring 应用配置 +spring: + # 应用名称配置 + application: + # 设置应用的名称为 user-service + name: user-service + # Spring Cloud 配置 + cloud: + config: + # 配置服务发现相关设置 + discovery: + # 启用配置服务发现功能 + enabled: true + # 指定配置服务的服务ID为 config-server + service-id: config-server + # 启用快速失败机制,当配置服务不可用时,应用将快速失败而不是等待 + fail-fast: true + + diff --git a/spring-data-jpa-read-write-separation/pom.xml b/spring-data-jpa-read-write-separation/pom.xml new file mode 100644 index 0000000..b69aa5a --- /dev/null +++ b/spring-data-jpa-read-write-separation/pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + + + com.mangmang + learning-nexus + 1.0.0 + + + spring-data-jpa-read-write-separation + jar + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-aop + + + org.projectlombok + lombok + true + + + mysql + mysql-connector-java + + + diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/SpringDataJpaReadWriteSeparationApplication.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/SpringDataJpaReadWriteSeparationApplication.java new file mode 100644 index 0000000..ba0cd87 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/SpringDataJpaReadWriteSeparationApplication.java @@ -0,0 +1,12 @@ +package com.mangmang; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class SpringDataJpaReadWriteSeparationApplication { + public static void main(String[] args) { + SpringApplication.run(SpringDataJpaReadWriteSeparationApplication.class, args); + } +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/DataSource.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/DataSource.java new file mode 100644 index 0000000..4b3e44e --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/DataSource.java @@ -0,0 +1,5 @@ +package com.mangmang.annotaion; + +public enum DataSource { + MASTER, SLAVE; +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/TargetDataSource.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/TargetDataSource.java new file mode 100644 index 0000000..fc82f66 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/annotaion/TargetDataSource.java @@ -0,0 +1,24 @@ +package com.mangmang.annotaion; + + +import java.lang.annotation.*; + +/** + * 数据源切换注解,用于在方法或类级别指定使用的数据源 + * 该注解可应用于类或方法上,运行时生效,并会保留在javadoc文档中 + * @see DataSource 数据源类型枚举,包含可用数据源定义(如MASTER主库、SLAVE从库等) + * 参数说明: + * @value 指定目标数据源名称,默认使用MASTER主库数据源 + * 通过该参数实现数据源动态切换,需配合AOP或拦截器实现具体切换逻辑 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TargetDataSource { + /** + * 数据源类型配置参数 + * @return 数据源枚举值,默认返回主库数据源标识 + */ + DataSource value() default DataSource.MASTER; +} + diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceAspect.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceAspect.java new file mode 100644 index 0000000..9da66b9 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceAspect.java @@ -0,0 +1,79 @@ +package com.mangmang.config; + +import com.mangmang.annotaion.DataSource; +import com.mangmang.annotaion.TargetDataSource; +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.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + + +/** + * 动态数据源切面类,用于实现基于注解的数据源动态切换功能 + * + * 本切面通过拦截带有@TargetDataSource注解的方法,在执行目标方法前切换数据源, + * 执行完成后自动清理数据源上下文,实现主从库的动态路由 + */ +@Slf4j +@Order(1) +@Aspect +@Component +public class DataSourceAspect { + + /** + * 定义切点:拦截所有标注@TargetDataSource注解的方法 + * + * @Pointcut 使用注解表达式定位需要拦截的方法,注解全路径为com.mangmang.annotaion.TargetDataSource + */ + @Pointcut("@annotation(com.mangmang.annotaion.TargetDataSource)") + public void dataSourcePointcut() { + + } + + /** + * 环绕通知方法,实现数据源的动态切换和清理 + * + * @param point 连接点对象,提供被拦截方法的相关信息 + * @return Object 目标方法的执行结果 + * @throws Throwable 目标方法可能抛出的异常 + * + * 执行流程: + * 1. 获取方法签名和注解信息 + * 2. 根据注解值设置数据源(无注解时默认主库) + * 3. 执行目标方法 + * 4. 清理线程数据源上下文 + */ + @Around("dataSourcePointcut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + // 获取方法元数据 + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + + // 解析目标数据源注解 + TargetDataSource ds = method.getAnnotation(TargetDataSource.class); + + // 设置数据源策略:有注解使用注解值,无注解使用默认主库 + if (ds == null) { + DynamicDataSource.DynamicDataSourceContextHolder.setDataSource(DataSource.MASTER.name()); + log.info("使用主库"); + } else { + DynamicDataSource.DynamicDataSourceContextHolder.setDataSource(ds.value().name()); + } + + try { + // 执行被拦截的目标方法 + return point.proceed(); + } finally { + // 确保线程数据源上下文清理,避免内存泄漏 + DynamicDataSource.DynamicDataSourceContextHolder.clearDataSource(); + log.info("清除数据源"); + } + } +} + diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceConfig.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceConfig.java new file mode 100644 index 0000000..9f1ac8b --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DataSourceConfig.java @@ -0,0 +1,75 @@ +package com.mangmang.config; + + +import org.springframework.beans.factory.annotation.Qualifier; +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.context.annotation.Primary; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * 数据源配置类,用于配置主从数据源和动态路由数据源 + */ +@Configuration +public class DataSourceConfig { + + /** + * 创建主数据源Bean + * @return 配置好的主数据源实例,自动绑定spring.datasource.master前缀的配置属性 + */ + @Bean + @ConfigurationProperties(prefix = "spring.datasource.master") + public DataSource masterDataSource() { + return DataSourceBuilder.create().build(); + } + + /** + * 创建从数据源Bean + * @return 配置好的从数据源实例,自动绑定spring.datasource.slave前缀的配置属性 + */ + @Bean + @ConfigurationProperties(prefix = "spring.datasource.slave") + public DataSource slaveDataSource() { + return DataSourceBuilder.create().build(); + } + + /** + * 创建动态数据源路由(主数据源作为默认数据源) + * @param masterDataSource 通过@Qualifier注入的主数据源实例 + * @param slaveDataSource 通过@Qualifier注入的从数据源实例 + * @return 配置好的动态数据源路由实例 + */ + @Bean + @Primary + public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, + @Qualifier("slaveDataSource") DataSource slaveDataSource) { + DynamicDataSource dynamicDataSource = new DynamicDataSource(); + // 构建数据源映射表 + Map dataSourceMap = new HashMap<>(2); + dataSourceMap.put(com.mangmang.annotaion.DataSource.MASTER.name(), masterDataSource); + dataSourceMap.put(com.mangmang.annotaion.DataSource.SLAVE.name(), slaveDataSource); + + // 设置默认数据源和完整数据源集合 + dynamicDataSource.setDefaultTargetDataSource(masterDataSource); + dynamicDataSource.setTargetDataSources(dataSourceMap); + + return dynamicDataSource; + } + + /** + * 创建事务管理器 + * @param dynamicDataSource 动态数据源路由实例 + * @return 配置好的事务管理器实例 + */ + @Bean + public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) { + return new DataSourceTransactionManager(dynamicDataSource); + } +} + diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DynamicDataSource.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DynamicDataSource.java new file mode 100644 index 0000000..f89df41 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/config/DynamicDataSource.java @@ -0,0 +1,52 @@ +package com.mangmang.config; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + + +/** + * 动态数据源路由实现类,继承Spring框架的AbstractRoutingDataSource + * 通过重写determineCurrentLookupKey方法实现多数据源动态切换 + */ +public class DynamicDataSource extends AbstractRoutingDataSource { + /** + * 获取当前线程绑定的数据源标识 + * @return String 数据源标识key,对应配置的数据源映射 + */ + @Override + protected Object determineCurrentLookupKey() { + return DynamicDataSourceContextHolder.getDataSource(); + } + + /** + * 数据源上下文持有器(线程安全) + * 使用ThreadLocal实现线程级数据源隔离,通过set/get/clear方法管理当前线程数据源标识 + */ + public static class DynamicDataSourceContextHolder { + // 线程本地变量存储数据源标识 + private final static ThreadLocal contextHolder = new ThreadLocal<>(); + + /** + * 绑定数据源标识到当前线程 + * @param dataSource 数据源标识key + */ + public static void setDataSource(String dataSource) { + contextHolder.set(dataSource); + } + + /** + * 获取当前线程绑定的数据源标识 + * @return String 当前数据源标识,可能为null + */ + public static String getDataSource() { + return contextHolder.get(); + } + + /** + * 清除当前线程的数据源绑定 + */ + public static void clearDataSource() { + contextHolder.remove(); + } + } +} + diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/controller/UserController.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/controller/UserController.java new file mode 100644 index 0000000..b7fe7f0 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/controller/UserController.java @@ -0,0 +1,38 @@ +package com.mangmang.controller; + +import com.mangmang.entity.User; +import com.mangmang.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + private final UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + @PostMapping + public ResponseEntity createUser(@RequestBody User user) { + return ResponseEntity.ok(userService.saveUser(user)); + } + + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable Long id) { + return userService.findById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllUsers() { + return ResponseEntity.ok(userService.findAllUsers()); + } +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/entity/User.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/entity/User.java new file mode 100644 index 0000000..344a5ad --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/entity/User.java @@ -0,0 +1,22 @@ +package com.mangmang.entity; + +import lombok.Data; + +import javax.persistence.*; +import java.io.Serializable; + +@Data +@Entity +@Table(name = "users") +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String username; + + private String email; +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/repository/UserRepository.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/repository/UserRepository.java new file mode 100644 index 0000000..f65fd78 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.mangmang.repository; + +import com.mangmang.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/UserService.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/UserService.java new file mode 100644 index 0000000..216f80e --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/UserService.java @@ -0,0 +1,15 @@ +package com.mangmang.service; + +import com.mangmang.entity.User; + +import java.util.List; +import java.util.Optional; + +public interface UserService { + + User saveUser(User user); + + Optional findById(Long id); + + List findAllUsers(); +} diff --git a/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/impl/UserServiceImpl.java b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..987b78b --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/java/com/mangmang/service/impl/UserServiceImpl.java @@ -0,0 +1,45 @@ +package com.mangmang.service.impl; + +import com.mangmang.annotaion.DataSource; +import com.mangmang.annotaion.TargetDataSource; +import com.mangmang.entity.User; +import com.mangmang.repository.UserRepository; +import com.mangmang.service.UserService; +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.Optional; + +@Service +public class UserServiceImpl implements UserService { + + + private final UserRepository userRepository; + + @Autowired + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional + public User saveUser(User user) { + return userRepository.save(user); + } + + @Transactional(readOnly = true) + @TargetDataSource(DataSource.SLAVE) + @Override + public Optional findById(Long id) { + return userRepository.findById(id); + } + + @Transactional(readOnly = true) + @TargetDataSource(DataSource.SLAVE) + @Override + public List findAllUsers() { + return userRepository.findAll(); + } +} diff --git a/spring-data-jpa-read-write-separation/src/main/resources/application.yml b/spring-data-jpa-read-write-separation/src/main/resources/application.yml new file mode 100644 index 0000000..3bc6209 --- /dev/null +++ b/spring-data-jpa-read-write-separation/src/main/resources/application.yml @@ -0,0 +1,19 @@ +spring: + datasource: + master: + jdbc-url: jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + slave: + jdbc-url: jdbc:mysql://localhost:3307/slave_db?useSSL=false&serverTimezone=UTC + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file diff --git a/swing-and-javafx/pom.xml b/swing-and-javafx/pom.xml new file mode 100644 index 0000000..f2f3612 --- /dev/null +++ b/swing-and-javafx/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.mangmang + learning-nexus + 1.0.0 + + + + swing-and-javafx + swing-and-javafx + jar + + + UTF-8 + 21 + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + com.mangmang.HelloWorldFX + + + + + + + diff --git a/swing-and-javafx/src/main/java/com/mangmang/HelloWordSwing.java b/swing-and-javafx/src/main/java/com/mangmang/HelloWordSwing.java new file mode 100644 index 0000000..a187329 --- /dev/null +++ b/swing-and-javafx/src/main/java/com/mangmang/HelloWordSwing.java @@ -0,0 +1,29 @@ +package com.mangmang; + +import javax.swing.*; +import java.awt.*; + + +/** + * Hello world! + * + */ +public class HelloWordSwing { + public static void main(String[] args) { + JFrame jFrame = new JFrame("Hello World Swing"); + jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + jFrame.setSize(300, 300); + + + JButton button = new JButton("点击我"); + button.addActionListener(e -> { + JOptionPane.showConfirmDialog(jFrame, "你点击了按钮"); + }); + + + jFrame.getContentPane().add(button, BorderLayout.CENTER); + + jFrame.setLocationRelativeTo(null); + jFrame.setVisible(true); + } +} diff --git a/swing-and-javafx/src/main/java/com/mangmang/HelloWorldFX.java b/swing-and-javafx/src/main/java/com/mangmang/HelloWorldFX.java new file mode 100644 index 0000000..015ba73 --- /dev/null +++ b/swing-and-javafx/src/main/java/com/mangmang/HelloWorldFX.java @@ -0,0 +1,28 @@ +package com.mangmang; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; + +public class HelloWorldFX extends Application { + @Override + public void start(Stage primaryStage) { + Button btn = new Button("点击我"); + btn.setOnAction(e -> System.out.println("你好,JavaFX!")); + + StackPane root = new StackPane(); + root.getChildren().add(btn); + + Scene scene = new Scene(root, 300, 250); + + primaryStage.setTitle("Hello JavaFX"); + primaryStage.setScene(scene); + primaryStage.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/swing-and-javafx/src/main/java/com/mangmang/ImageConverterApp.java b/swing-and-javafx/src/main/java/com/mangmang/ImageConverterApp.java new file mode 100644 index 0000000..02792f0 --- /dev/null +++ b/swing-and-javafx/src/main/java/com/mangmang/ImageConverterApp.java @@ -0,0 +1,267 @@ +package com.mangmang; + +import javafx.application.Application; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.*; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class ImageConverterApp extends Application { + + private ImageView imagePreview; + private Label statusLabel; + private Label imageInfoLabel; + private ComboBox formatComboBox; + private TextField outputDirField; + private List selectedFiles; + + // 支持的图像格式 + private final String[] supportedFormats = {"JPEG", "PNG", "BMP", "GIF", "TIFF"}; + + @Override + public void start(Stage primaryStage) { + primaryStage.setTitle("图像格式转换工具"); + + // 创建主布局 + BorderPane mainLayout = new BorderPane(); + mainLayout.setPadding(new Insets(10)); + + // 创建顶部工具栏 + HBox toolbar = createToolbar(); + mainLayout.setTop(toolbar); + + // 创建中间内容区域 + VBox centerContent = createCenterContent(); + mainLayout.setCenter(centerContent); + + // 创建底部状态栏 + HBox statusBar = createStatusBar(); + mainLayout.setBottom(statusBar); + + // 设置场景 + Scene scene = new Scene(mainLayout, 800, 600); + primaryStage.setScene(scene); + primaryStage.show(); + } + + private HBox createToolbar() { + HBox toolbar = new HBox(10); + toolbar.setPadding(new Insets(10)); + + // 导入按钮 + Button importButton = new Button("导入图片"); + importButton.setOnAction(e -> importImages()); + + // 格式选择下拉框 + Label formatLabel = new Label("转换格式:"); + formatComboBox = new ComboBox<>(); + formatComboBox.getItems().addAll(supportedFormats); + formatComboBox.setValue(supportedFormats[0]); + + // 输出目录 + Label outputDirLabel = new Label("输出目录:"); + outputDirField = new TextField(System.getProperty("user.home")); + outputDirField.setPrefWidth(200); + Button browseButton = new Button("浏览..."); + browseButton.setOnAction(e -> browseOutputDirectory()); + + // 转换按钮 + Button convertButton = new Button("开始转换"); + convertButton.setOnAction(e -> convertImages()); + + toolbar.getChildren().addAll( + importButton, new Separator(javafx.geometry.Orientation.VERTICAL), + formatLabel, formatComboBox, new Separator(javafx.geometry.Orientation.VERTICAL), + outputDirLabel, outputDirField, browseButton, new Separator(javafx.geometry.Orientation.VERTICAL), + convertButton + ); + + return toolbar; + } + + private VBox createCenterContent() { + VBox centerContent = new VBox(10); + centerContent.setPadding(new Insets(10)); + + // 图片预览区域 + Label previewLabel = new Label("预览:"); + imagePreview = new ImageView(); + imagePreview.setFitHeight(300); + imagePreview.setFitWidth(400); + imagePreview.setPreserveRatio(true); + + // 图片信息标签 + imageInfoLabel = new Label("未加载图片"); + + // 质量设置滑块 + HBox qualityBox = new HBox(10); + Label qualityLabel = new Label("图像质量:"); + Slider qualitySlider = new Slider(0, 100, 80); + qualitySlider.setShowTickLabels(true); + qualitySlider.setShowTickMarks(true); + qualitySlider.setMajorTickUnit(25); + qualitySlider.setMinorTickCount(5); + Label qualityValueLabel = new Label("80%"); + qualitySlider.valueProperty().addListener((obs, oldVal, newVal) -> qualityValueLabel.setText(String.format("%.0f%%", newVal.doubleValue()))); + qualityBox.getChildren().addAll(qualityLabel, qualitySlider, qualityValueLabel); + + // 创建拖放区域 + VBox dropZone = new VBox(); + dropZone.getChildren().addAll(imagePreview); + dropZone.setStyle("-fx-border-color: #cccccc; -fx-border-style: dashed; -fx-background-color: #f7f7f7;"); + dropZone.setPadding(new Insets(20)); + dropZone.setAlignment(javafx.geometry.Pos.CENTER); + + // 设置拖放功能 + setupDragAndDrop(dropZone); + + centerContent.getChildren().addAll(previewLabel, dropZone, imageInfoLabel, qualityBox); + return centerContent; + } + + private HBox createStatusBar() { + HBox statusBar = new HBox(); + statusBar.setPadding(new Insets(5)); + statusBar.setStyle("-fx-background-color: #e0e0e0;"); + + statusLabel = new Label("就绪"); + statusBar.getChildren().add(statusLabel); + + return statusBar; + } + + private void setupDragAndDrop(VBox dropZone) { + dropZone.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY); + } + event.consume(); + }); + + dropZone.setOnDragDropped(event -> { + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasFiles()) { + selectedFiles = db.getFiles(); + success = true; + updatePreview(selectedFiles.get(0)); + } + event.setDropCompleted(success); + event.consume(); + }); + } + + private void importImages() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("选择图片文件"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("图像文件", "*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff") + ); + selectedFiles = fileChooser.showOpenMultipleDialog(null); + + if (selectedFiles != null && !selectedFiles.isEmpty()) { + updatePreview(selectedFiles.get(0)); + statusLabel.setText("已选择 " + selectedFiles.size() + " 个文件"); + } + } + + private void browseOutputDirectory() { + javafx.stage.DirectoryChooser directoryChooser = new javafx.stage.DirectoryChooser(); + directoryChooser.setTitle("选择输出目录"); + File selectedDirectory = directoryChooser.showDialog(null); + + if (selectedDirectory != null) { + outputDirField.setText(selectedDirectory.getAbsolutePath()); + } + } + + private void updatePreview(File imageFile) { + try { + Image image = new Image(imageFile.toURI().toString()); + imagePreview.setImage(image); + + // 更新图像信息 + String format = imageFile.getName().substring(imageFile.getName().lastIndexOf('.') + 1).toUpperCase(); + imageInfoLabel.setText(String.format( + "文件名: %s\n尺寸: %.0fx%.0f\n格式: %s\n大小: %.2f KB", + imageFile.getName(), + image.getWidth(), + image.getHeight(), + format, + imageFile.length() / 1024.0 + )); + } catch (Exception e) { + statusLabel.setText("加载图片预览失败: " + e.getMessage()); + } + } + + private void convertImages() { + if (selectedFiles == null || selectedFiles.isEmpty()) { + showAlert("请先选择图片文件"); + return; + } + + String targetFormat = formatComboBox.getValue().toLowerCase(); + String outputDirectory = outputDirField.getText(); + File outputDir = new File(outputDirectory); + + if (!outputDir.exists() || !outputDir.isDirectory()) { + if (!outputDir.mkdirs()) { + showAlert("创建输出目录失败"); + return; + } + } + + int convertedCount = 0; + for (File file : selectedFiles) { + try { + BufferedImage image = ImageIO.read(file); + if (image == null) { + statusLabel.setText("无法读取图片: " + file.getName()); + continue; + } + + String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')); + File outputFile = new File(outputDir, fileName + "." + targetFormat); + + // 对于JPEG格式,可以设置质量 + ImageIO.write(image, targetFormat, outputFile); + + convertedCount++; + } catch (IOException e) { + statusLabel.setText("转换失败: " + e.getMessage()); + } + } + + statusLabel.setText("转换完成,成功转换 " + convertedCount + " 个文件"); + showAlert("转换完成", "成功转换 " + convertedCount + " 个文件到 " + outputDirectory, Alert.AlertType.INFORMATION); + } + + private void showAlert(String message) { + showAlert("提示", message, Alert.AlertType.WARNING); + } + + private void showAlert(String title, String message, Alert.AlertType alertType) { + Alert alert = new Alert(alertType); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/websocket/pom.xml b/websocket/pom.xml new file mode 100644 index 0000000..33696d8 --- /dev/null +++ b/websocket/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + com.mangmang + learning-nexus + 1.0.0 + + + websocket + jar + + websocket + http://maven.apache.org + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + org.projectlombok + lombok + true + + + diff --git a/websocket/src/main/java/com/mangmang/WebSocketDemoApplication.java b/websocket/src/main/java/com/mangmang/WebSocketDemoApplication.java new file mode 100644 index 0000000..1f80343 --- /dev/null +++ b/websocket/src/main/java/com/mangmang/WebSocketDemoApplication.java @@ -0,0 +1,13 @@ +package com.mangmang; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebSocketDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(WebSocketDemoApplication.class, args); + } +} diff --git a/websocket/src/main/java/com/mangmang/config/WebSocketConfig.java b/websocket/src/main/java/com/mangmang/config/WebSocketConfig.java new file mode 100644 index 0000000..fd65761 --- /dev/null +++ b/websocket/src/main/java/com/mangmang/config/WebSocketConfig.java @@ -0,0 +1,27 @@ +package com.mangmang.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // 注册STOMP协议的节点(endpoint),并指定使用SockJS协议 + registry.addEndpoint("/ws").setAllowedOrigins("http://10.6.212.39:5173/"); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + // 配置消息代理,前缀为/topic的消息将会被路由到消息代理 + registry.enableSimpleBroker("/topic"); + // 以/app开头的消息将会被路由到@MessageMapping注解的方法中 + registry.setApplicationDestinationPrefixes("/app"); + } + +} diff --git a/websocket/src/main/java/com/mangmang/controller/MessageController.java b/websocket/src/main/java/com/mangmang/controller/MessageController.java new file mode 100644 index 0000000..a193ae7 --- /dev/null +++ b/websocket/src/main/java/com/mangmang/controller/MessageController.java @@ -0,0 +1,36 @@ +package com.mangmang.controller; + +import com.mangmang.model.Message; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.stereotype.Controller; + +import java.util.Objects; + + + +@Slf4j +@Controller +public class MessageController { + + @MessageMapping("/chat.sendMessage") + @SendTo("/topic/public") + public Message sendMessage(@Payload Message message) { + log.info(message.toString()); + return message; + } + + + @MessageMapping("/chat.addUser") + @SendTo("/topic/public") + public Message addUser(@Payload Message message, SimpMessageHeaderAccessor headerAccessor) { + log.info(message.toString()); + // 将用户名添加到WebSocket会话 + Objects.requireNonNull(headerAccessor.getSessionAttributes()).put("username", message.getSender()); + return message; + } +} + diff --git a/websocket/src/main/java/com/mangmang/listener/WebSocketEventListener.java b/websocket/src/main/java/com/mangmang/listener/WebSocketEventListener.java new file mode 100644 index 0000000..4884228 --- /dev/null +++ b/websocket/src/main/java/com/mangmang/listener/WebSocketEventListener.java @@ -0,0 +1,43 @@ +package com.mangmang.listener; + +import com.mangmang.model.Message; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WebSocketEventListener { + + private final SimpMessageSendingOperations messagingTemplate; + + @EventListener + public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { + // 获取会话属性 + SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(event.getMessage()); + + // 从会话中获取用户名 + String username = (String) Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("username"); + + if (username != null) { + log.info("用户断开连接: {}", username); + + // 创建一个离开消息 + Message message = Message.builder() + .type(Message.MessageType.LEAVE) + .sender(username) + .content("下线了") + .build(); + + // 发送消息到公共主题 + messagingTemplate.convertAndSend("/topic/public", message); + } + } +} diff --git a/websocket/src/main/java/com/mangmang/model/Message.java b/websocket/src/main/java/com/mangmang/model/Message.java new file mode 100644 index 0000000..2cbce47 --- /dev/null +++ b/websocket/src/main/java/com/mangmang/model/Message.java @@ -0,0 +1,25 @@ +package com.mangmang.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import lombok.NoArgsConstructor; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Message { + private String content; + private String sender; + private MessageType type; + + + public enum MessageType { + CHAT, + JOIN, + LEAVE + } +} diff --git a/websocket/src/main/resources/application.yml b/websocket/src/main/resources/application.yml new file mode 100644 index 0000000..d3a2a79 --- /dev/null +++ b/websocket/src/main/resources/application.yml @@ -0,0 +1,6 @@ +server: + port: 8099 +# application.yml +logging: + level: + org.springframework.web.socket: DEBUG \ No newline at end of file