代码提交
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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/
|
||||
|
||||
201
LICENSE
201
LICENSE
@@ -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.
|
||||
66
pom.xml
Normal file
66
pom.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>learning-nexus</name>
|
||||
|
||||
<!-- 🌱 管理子模块 -->
|
||||
<modules>
|
||||
<module>reactive-programming</module>
|
||||
<module>spring-data-jpa-read-write-separation</module>
|
||||
<module>spring-cloud-demo</module>
|
||||
<module>swing-and-javafx</module>
|
||||
<module>websocket</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-boot.version>2.6.7</spring-boot.version>
|
||||
<spring-cloud.version>2021.0.2</spring-cloud.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<thrift.version>0.16.0</thrift.version>
|
||||
</properties>
|
||||
|
||||
<!-- ✅ 统一管理依赖版本 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<!-- 🔧 插件和版本统一配置 -->
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
50
reactive-programming/pom.xml
Normal file
50
reactive-programming/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.mangmang</groupId>
|
||||
<artifactId>reactive-programming</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!-- 确保添加 spring-boot-starter-web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Spring Boot Maven 插件 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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!";
|
||||
}
|
||||
}
|
||||
@@ -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<Product> getAllProducts() {
|
||||
return productService.getAllProducts();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Mono<ResponseEntity<Product>> getProductById(@PathVariable String id) {
|
||||
return productService.getProductById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping("/category/{category}")
|
||||
public Flux<Product> getProductsByCategory(@PathVariable String category) {
|
||||
return productService.getProductsByCategory(category);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<Product> streamAllProducts() {
|
||||
return productService.getAllProducts();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<Product> createProduct(@RequestBody Product product) {
|
||||
return productService.saveProduct(product);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public Mono<ResponseEntity<Product>> 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<ResponseEntity<Void>> deleteProduct(@PathVariable String id) {
|
||||
return productService.getProductById(id)
|
||||
.flatMap(existingProduct ->
|
||||
productService.deleteProduct(id)
|
||||
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
|
||||
)
|
||||
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Product, String> {
|
||||
Flux<Product> findByCategory(String category);
|
||||
}
|
||||
@@ -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<Product> getAllProducts() {
|
||||
return productRepository.findAll();
|
||||
}
|
||||
|
||||
public Mono<Product> getProductById(String id) {
|
||||
return productRepository.findById(id);
|
||||
}
|
||||
|
||||
public Flux<Product> getProductsByCategory(String category) {
|
||||
return productRepository.findByCategory(category);
|
||||
}
|
||||
|
||||
public Mono<Product> saveProduct(Product product) {
|
||||
return productRepository.save(product);
|
||||
}
|
||||
|
||||
public Mono<Void> deleteProduct(String id) {
|
||||
return productRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
34
spring-cloud-demo/config-server/pom.xml
Normal file
34
spring-cloud-demo/config-server/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>config-server</name>
|
||||
<artifactId>config-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-config-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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/
|
||||
33
spring-cloud-demo/eureka-server/pom.xml
Normal file
33
spring-cloud-demo/eureka-server/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>eureka-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>eureka-server</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
46
spring-cloud-demo/gateway-service/pom.xml
Normal file
46
spring-cloud-demo/gateway-service/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<name>gateway-service</name>
|
||||
<artifactId>gateway-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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: "*"
|
||||
@@ -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
|
||||
55
spring-cloud-demo/order-service/pom.xml
Normal file
55
spring-cloud-demo/order-service/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<name>order-service</name>
|
||||
<artifactId>order-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Order> 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<Order> 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<Order> 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;
|
||||
}
|
||||
}
|
||||
@@ -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: "*"
|
||||
@@ -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
|
||||
34
spring-cloud-demo/pom.xml
Normal file
34
spring-cloud-demo/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>spring-cloud-demo</name>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>eureka-server</module>
|
||||
<module>config-server</module>
|
||||
<module>user-service</module>
|
||||
<module>order-service</module>
|
||||
<module>gateway-service</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
53
spring-cloud-demo/user-service/pom.xml
Normal file
53
spring-cloud-demo/user-service/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>spring-cloud-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>user-service</name>
|
||||
<artifactId>user-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<User> users = Arrays.asList(
|
||||
new User(1L, "张三", "zhangsan@example.com"),
|
||||
new User(2L, "李四", "lisi@example.com"),
|
||||
new User(3L, "王五", "wangwu@example.com")
|
||||
);
|
||||
|
||||
@GetMapping
|
||||
public List<User> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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: "*"
|
||||
@@ -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
|
||||
|
||||
|
||||
41
spring-data-jpa-read-write-separation/pom.xml
Normal file
41
spring-data-jpa-read-write-separation/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-data-jpa-read-write-separation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mangmang.annotaion;
|
||||
|
||||
public enum DataSource {
|
||||
MASTER, SLAVE;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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("清除数据源");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Object, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<User> createUser(@RequestBody User user) {
|
||||
return ResponseEntity.ok(userService.saveUser(user));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<User> getUserById(@PathVariable Long id) {
|
||||
return userService.findById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<User>> getAllUsers() {
|
||||
return ResponseEntity.ok(userService.findAllUsers());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<User, Long> {
|
||||
}
|
||||
@@ -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<User> findById(Long id);
|
||||
|
||||
List<User> findAllUsers();
|
||||
}
|
||||
@@ -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<User> findById(Long id) {
|
||||
return userRepository.findById(id);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@TargetDataSource(DataSource.SLAVE)
|
||||
@Override
|
||||
public List<User> findAllUsers() {
|
||||
return userRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
47
swing-and-javafx/pom.xml
Normal file
47
swing-and-javafx/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>swing-and-javafx</name>
|
||||
<artifactId>swing-and-javafx</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<javafx.version>21</javafx.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>com.mangmang.HelloWorldFX</mainClass> <!-- 替换成你的主类 -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> formatComboBox;
|
||||
private TextField outputDirField;
|
||||
private List<File> 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);
|
||||
}
|
||||
}
|
||||
31
websocket/pom.xml
Normal file
31
websocket/pom.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.mangmang</groupId>
|
||||
<artifactId>learning-nexus</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>websocket</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>websocket</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
websocket/src/main/java/com/mangmang/model/Message.java
Normal file
25
websocket/src/main/java/com/mangmang/model/Message.java
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
6
websocket/src/main/resources/application.yml
Normal file
6
websocket/src/main/resources/application.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
server:
|
||||
port: 8099
|
||||
# application.yml
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web.socket: DEBUG
|
||||
Reference in New Issue
Block a user