08ea5fbe98
添加用户管理视图、API和状态管理文件
1738 lines
47 KiB
Markdown
1738 lines
47 KiB
Markdown
# 双应用架构重构实施计划
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**目标:** 将现有的单体应用重构为客户端和后台管理双应用架构,实现独立部署、业务逻辑复用、网关路由区分。
|
|
|
|
**架构:** 采用双应用方案,客户端应用(client-app)和后台管理应用(admin-app)独立部署,共享业务逻辑层(biz),通过网关(gateway)路由区分 /client/** 和 /admin/** 请求。
|
|
|
|
**技术栈:** Spring Boot 3.5.10 + WebFlux + R2DBC + PostgreSQL + Redis + JWT + Actuator
|
|
|
|
---
|
|
|
|
## 阶段一:模块拆分(1-2天)
|
|
|
|
### Task 1: 创建客户端应用模块
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-client-app/pom.xml`
|
|
- Create: `everything-is-suitable-client-app/src/main/java/io/destiny/client/ClientApplication.java`
|
|
- Create: `everything-is-suitable-client-app/src/main/resources/application.yml`
|
|
- Create: `everything-is-suitable-client-app/src/main/resources/application-dev.yml`
|
|
- Create: `everything-is-suitable-client-app/src/main/resources/application-prod.yml`
|
|
|
|
**Step 1: 创建客户端应用 pom.xml**
|
|
|
|
```xml
|
|
<?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>
|
|
|
|
<parent>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-api</artifactId>
|
|
<version>1.0.0</version>
|
|
</parent>
|
|
|
|
<artifactId>everything-is-suitable-client-app</artifactId>
|
|
<packaging>jar</packaging>
|
|
|
|
<name>Everything Is Suitable Client App</name>
|
|
<description>Client application for Everything Is Suitable API</description>
|
|
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-client-api</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-biz</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-db</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-common</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.micrometer</groupId>
|
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.postgresql</groupId>
|
|
<artifactId>r2dbc-postgresql</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.postgresql</groupId>
|
|
<artifactId>postgresql</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-test</artifactId>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
</dependencies>
|
|
|
|
<build>
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
<configuration>
|
|
<mainClass>io.destiny.client.ClientApplication</mainClass>
|
|
</configuration>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</project>
|
|
```
|
|
|
|
**Step 2: 创建客户端应用启动类**
|
|
|
|
```java
|
|
package io.destiny.client;
|
|
|
|
import org.springframework.boot.SpringApplication;
|
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
|
import org.springframework.context.annotation.ComponentScan;
|
|
|
|
@SpringBootApplication
|
|
@ConfigurationPropertiesScan(basePackages = "io.destiny")
|
|
@ComponentScan(basePackages = "io.destiny")
|
|
public class ClientApplication {
|
|
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(ClientApplication.class, args);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 创建客户端应用配置文件**
|
|
|
|
```yaml
|
|
server:
|
|
port: 8081
|
|
|
|
spring:
|
|
application:
|
|
name: client-app
|
|
r2dbc:
|
|
url: r2dbc:postgresql://localhost:5432/everything_suitable
|
|
username: ${DB_USERNAME:postgres}
|
|
password: ${DB_PASSWORD:postgres}
|
|
pool:
|
|
initial-size: 10
|
|
max-size: 50
|
|
flyway:
|
|
enabled: true
|
|
locations: classpath:db/migration
|
|
|
|
management:
|
|
endpoints:
|
|
web:
|
|
exposure:
|
|
include: health,info,metrics,env,loggers
|
|
base-path: /actuator
|
|
endpoint:
|
|
health:
|
|
show-details: always
|
|
metrics:
|
|
tags:
|
|
application: ${spring.application.name}
|
|
environment: ${spring.profiles.active}
|
|
|
|
logging:
|
|
level:
|
|
io.destiny: DEBUG
|
|
org.springframework.r2dbc: DEBUG
|
|
```
|
|
|
|
**Step 4: 更新父 pom.xml 添加新模块**
|
|
|
|
```xml
|
|
<modules>
|
|
<module>everything-is-suitable-client-app</module>
|
|
<module>everything-is-suitable-admin-app</module>
|
|
<module>everything-is-suitable-app</module>
|
|
<module>everything-is-suitable-common</module>
|
|
<module>everything-is-suitable-db</module>
|
|
<module>everything-is-suitable-sys</module>
|
|
<module>everything-is-suitable-biz</module>
|
|
<module>everything-is-suitable-gateway</module>
|
|
<module>everything-is-suitable-client</module>
|
|
<module>everything-is-suitable-statistics</module>
|
|
<module>everything-is-suitable-client-api</module>
|
|
<module>everything-is-suitable-admin-api</module>
|
|
</modules>
|
|
```
|
|
|
|
**Step 5: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-client-app -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 6: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-client-app/
|
|
git add pom.xml
|
|
git commit -m "feat: create client-app module"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: 创建后台管理应用模块
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin-app/pom.xml`
|
|
- Create: `everything-is-suitable-admin-app/src/main/java/io/destiny/admin/AdminApplication.java`
|
|
- Create: `everything-is-suitable-admin-app/src/main/resources/application.yml`
|
|
- Create: `everything-is-suitable-admin-app/src/main/resources/application-dev.yml`
|
|
- Create: `everything-is-suitable-admin-app/src/main/resources/application-prod.yml`
|
|
|
|
**Step 1: 创建后台管理应用 pom.xml**
|
|
|
|
```xml
|
|
<?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>
|
|
|
|
<parent>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-api</artifactId>
|
|
<version>1.0.0</version>
|
|
</parent>
|
|
|
|
<artifactId>everything-is-suitable-admin-app</artifactId>
|
|
<packaging>jar</packaging>
|
|
|
|
<name>Everything Is Suitable Admin App</name>
|
|
<description>Admin application for Everything Is Suitable API</description>
|
|
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-admin-api</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-biz</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-sys</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-statistics</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-db</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-common</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.micrometer</groupId>
|
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.postgresql</groupId>
|
|
<artifactId>r2dbc-postgresql</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.postgresql</groupId>
|
|
<artifactId>postgresql</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-test</artifactId>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
</dependencies>
|
|
|
|
<build>
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
<configuration>
|
|
<mainClass>io.destiny.admin.AdminApplication</mainClass>
|
|
</configuration>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</project>
|
|
```
|
|
|
|
**Step 2: 创建后台管理应用启动类**
|
|
|
|
```java
|
|
package io.destiny.admin;
|
|
|
|
import org.springframework.boot.SpringApplication;
|
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
|
import org.springframework.context.annotation.ComponentScan;
|
|
|
|
@SpringBootApplication
|
|
@ConfigurationPropertiesScan(basePackages = "io.destiny")
|
|
@ComponentScan(basePackages = "io.destiny")
|
|
public class AdminApplication {
|
|
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(AdminApplication.class, args);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 创建后台管理应用配置文件**
|
|
|
|
```yaml
|
|
server:
|
|
port: 8082
|
|
|
|
spring:
|
|
application:
|
|
name: admin-app
|
|
r2dbc:
|
|
url: r2dbc:postgresql://localhost:5432/everything_suitable
|
|
username: ${DB_USERNAME:postgres}
|
|
password: ${DB_PASSWORD:postgres}
|
|
pool:
|
|
initial-size: 10
|
|
max-size: 50
|
|
flyway:
|
|
enabled: true
|
|
locations: classpath:db/migration
|
|
|
|
management:
|
|
endpoints:
|
|
web:
|
|
exposure:
|
|
include: health,info,metrics,env,loggers
|
|
base-path: /actuator
|
|
endpoint:
|
|
health:
|
|
show-details: always
|
|
metrics:
|
|
tags:
|
|
application: ${spring.application.name}
|
|
environment: ${spring.profiles.active}
|
|
|
|
logging:
|
|
level:
|
|
io.destiny: DEBUG
|
|
org.springframework.r2dbc: DEBUG
|
|
```
|
|
|
|
**Step 4: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-admin-app -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 5: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin-app/
|
|
git commit -m "feat: create admin-app module"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: 创建客户端接口层模块
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-client-api/pom.xml`
|
|
- Create: `everything-is-suitable-client-api/src/main/java/io/destiny/client/api/config/ClientRouter.java`
|
|
- Create: `everything-is-suitable-client-api/src/main/java/io/destiny/client/api/handler/ClientFortuneHandler.java`
|
|
- Create: `everything-is-suitable-client-api/src/main/java/io/destiny/client/api/handler/ClientAuthHandler.java`
|
|
|
|
**Step 1: 创建客户端接口层 pom.xml**
|
|
|
|
```xml
|
|
<?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>
|
|
|
|
<parent>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-api</artifactId>
|
|
<version>1.0.0</version>
|
|
</parent>
|
|
|
|
<artifactId>everything-is-suitable-client-api</artifactId>
|
|
<packaging>jar</packaging>
|
|
|
|
<name>Everything Is Suitable Client API</name>
|
|
<description>Client API layer for Everything Is Suitable API</description>
|
|
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-biz</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-common</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-validation</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springdoc</groupId>
|
|
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-test</artifactId>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
</dependencies>
|
|
|
|
<build>
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
<configuration>
|
|
<skip>true</skip>
|
|
</configuration>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</project>
|
|
```
|
|
|
|
**Step 2: 创建客户端路由配置**
|
|
|
|
```java
|
|
package io.destiny.client.api.config;
|
|
|
|
import io.destiny.client.api.handler.ClientAuthHandler;
|
|
import io.destiny.client.api.handler.ClientFortuneHandler;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
|
|
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
|
|
|
@Configuration
|
|
public class ClientRouter {
|
|
|
|
@Bean
|
|
public RouterFunction<ServerResponse> clientRoutes(ClientAuthHandler authHandler,
|
|
ClientFortuneHandler fortuneHandler) {
|
|
return route()
|
|
.POST("/api/auth/login", authHandler::login)
|
|
.POST("/api/auth/register", authHandler::register)
|
|
.GET("/api/fortune/daily", fortuneHandler::getDailyFortune)
|
|
.GET("/api/fortune/monthly", fortuneHandler::getMonthlyFortune)
|
|
.GET("/api/fortune/yearly", fortuneHandler::getYearlyFortune)
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 创建客户端认证处理器**
|
|
|
|
```java
|
|
package io.destiny.client.api.handler;
|
|
|
|
import io.destiny.biz.dto.FortuneRequest;
|
|
import io.destiny.biz.dto.FortuneResponse;
|
|
import io.destiny.biz.service.IFortuneAnalysisService;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class ClientAuthHandler {
|
|
|
|
public Mono<ServerResponse> login(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Login endpoint - to be implemented\"}");
|
|
}
|
|
|
|
public Mono<ServerResponse> register(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Register endpoint - to be implemented\"}");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: 创建客户端命理处理器**
|
|
|
|
```java
|
|
package io.destiny.client.api.handler;
|
|
|
|
import io.destiny.biz.dto.FortuneRequest;
|
|
import io.destiny.biz.dto.FortuneResponse;
|
|
import io.destiny.biz.service.IFortuneAnalysisService;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class ClientFortuneHandler {
|
|
|
|
private final IFortuneAnalysisService fortuneService;
|
|
|
|
public ClientFortuneHandler(IFortuneAnalysisService fortuneService) {
|
|
this.fortuneService = fortuneService;
|
|
}
|
|
|
|
public Mono<ServerResponse> getDailyFortune(ServerRequest request) {
|
|
String userId = request.queryParam("userId").orElse("");
|
|
String date = request.queryParam("date").orElse("");
|
|
|
|
FortuneRequest fortuneRequest = new FortuneRequest();
|
|
fortuneRequest.setUserId(userId);
|
|
|
|
return fortuneService.getDailyFortune(fortuneRequest)
|
|
.flatMap(response -> ServerResponse.ok()
|
|
.bodyValue(response))
|
|
.onErrorResume(e -> ServerResponse.badRequest()
|
|
.bodyValue("{\"error\":\"" + e.getMessage() + "\"}"));
|
|
}
|
|
|
|
public Mono<ServerResponse> getMonthlyFortune(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Monthly fortune - to be implemented\"}");
|
|
}
|
|
|
|
public Mono<ServerResponse> getYearlyFortune(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Yearly fortune - to be implemented\"}");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-client-api -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 6: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-client-api/
|
|
git commit -m "feat: create client-api module"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: 创建后台管理接口层模块
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin-api/pom.xml`
|
|
- Create: `everything-is-suitable-admin-api/src/main/java/io/destiny/admin/api/config/AdminRouter.java`
|
|
- Create: `everything-is-suitable-admin-api/src/main/java/io/destiny/admin/api/handler/AdminUserHandler.java`
|
|
- Create: `everything-is-suitable-admin-api/src/main/java/io/destiny/admin/api/handler/AdminStatisticsHandler.java`
|
|
|
|
**Step 1: 创建后台管理接口层 pom.xml**
|
|
|
|
```xml
|
|
<?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>
|
|
|
|
<parent>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-api</artifactId>
|
|
<version>1.0.0</version>
|
|
</parent>
|
|
|
|
<artifactId>everything-is-suitable-admin-api</artifactId>
|
|
<packaging>jar</packaging>
|
|
|
|
<name>Everything Is Suitable Admin API</name>
|
|
<description>Admin API layer for Everything Is Suitable API</description>
|
|
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-biz</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-sys</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-statistics</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.destiny</groupId>
|
|
<artifactId>everything-is-suitable-common</artifactId>
|
|
<version>${project.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-validation</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springdoc</groupId>
|
|
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-test</artifactId>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
</dependencies>
|
|
|
|
<build>
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
<configuration>
|
|
<skip>true</skip>
|
|
</configuration>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</project>
|
|
```
|
|
|
|
**Step 2: 创建后台管理路由配置**
|
|
|
|
```java
|
|
package io.destiny.admin.api.config;
|
|
|
|
import io.destiny.admin.api.handler.AdminStatisticsHandler;
|
|
import io.destiny.admin.api.handler.AdminUserHandler;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
|
|
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
|
|
|
@Configuration
|
|
public class AdminRouter {
|
|
|
|
@Bean
|
|
public RouterFunction<ServerResponse> adminRoutes(AdminUserHandler userHandler,
|
|
AdminStatisticsHandler statisticsHandler) {
|
|
return route()
|
|
.GET("/api/users", userHandler::listUsers)
|
|
.POST("/api/users", userHandler::createUser)
|
|
.GET("/api/statistics", statisticsHandler::getStatistics)
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 创建后台管理用户处理器**
|
|
|
|
```java
|
|
package io.destiny.admin.api.handler;
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class AdminUserHandler {
|
|
|
|
public Mono<ServerResponse> listUsers(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"List users - to be implemented\"}");
|
|
}
|
|
|
|
public Mono<ServerResponse> createUser(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Create user - to be implemented\"}");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: 创建后台管理统计处理器**
|
|
|
|
```java
|
|
package io.destiny.admin.api.handler;
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class AdminStatisticsHandler {
|
|
|
|
public Mono<ServerResponse> getStatistics(ServerRequest request) {
|
|
return ServerResponse.ok()
|
|
.bodyValue("{\"message\":\"Statistics - to be implemented\"}");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-admin-api -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 6: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin-api/
|
|
git commit -m "feat: create admin-api module"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段二:接口层迁移(2-3天)
|
|
|
|
### Task 5: 迁移现有 Handler 到客户端接口层
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-gateway/src/main/java/io/destiny/gateway/handler/ClientHandler.java`
|
|
- Create: `everything-is-suitable-client-api/src/main/java/io/destiny/client/api/handler/ClientAlmanacHandler.java`
|
|
|
|
**Step 1: 检查现有 ClientHandler 内容**
|
|
|
|
Run: `cat everything-is-suitable-gateway/src/main/java/io/destiny/gateway/handler/ClientHandler.java`
|
|
|
|
Expected: 查看现有客户端 Handler 的实现
|
|
|
|
**Step 2: 创建客户端黄历处理器**
|
|
|
|
```java
|
|
package io.destiny.client.api.handler;
|
|
|
|
import io.destiny.biz.dto.AlmanacRequest;
|
|
import io.destiny.biz.dto.AlmanacResponse;
|
|
import io.destiny.biz.service.IAlmanacService;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class ClientAlmanacHandler {
|
|
|
|
private final IAlmanacService almanacService;
|
|
|
|
public ClientAlmanacHandler(IAlmanacService almanacService) {
|
|
this.almanacService = almanacService;
|
|
}
|
|
|
|
public Mono<ServerResponse> getAlmanac(ServerRequest request) {
|
|
return request.bodyToMono(AlmanacRequest.class)
|
|
.flatMap(almanacService::getAlmanac)
|
|
.flatMap(response -> ServerResponse.ok()
|
|
.bodyValue(response))
|
|
.onErrorResume(e -> ServerResponse.badRequest()
|
|
.bodyValue("{\"error\":\"" + e.getMessage() + "\"}"));
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 更新客户端路由**
|
|
|
|
Modify: `everything-is-suitable-client-api/src/main/java/io/destiny/client/api/config/ClientRouter.java`
|
|
|
|
```java
|
|
package io.destiny.client.api.config;
|
|
|
|
import io.destiny.client.api.handler.ClientAlmanacHandler;
|
|
import io.destiny.client.api.handler.ClientAuthHandler;
|
|
import io.destiny.client.api.handler.ClientFortuneHandler;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
|
|
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
|
|
|
@Configuration
|
|
public class ClientRouter {
|
|
|
|
@Bean
|
|
public RouterFunction<ServerResponse> clientRoutes(ClientAuthHandler authHandler,
|
|
ClientFortuneHandler fortuneHandler,
|
|
ClientAlmanacHandler almanacHandler) {
|
|
return route()
|
|
.POST("/api/auth/login", authHandler::login)
|
|
.POST("/api/auth/register", authHandler::register)
|
|
.GET("/api/fortune/daily", fortuneHandler::getDailyFortune)
|
|
.GET("/api/fortune/monthly", fortuneHandler::getMonthlyFortune)
|
|
.GET("/api/fortune/yearly", fortuneHandler::getYearlyFortune)
|
|
.POST("/api/almanac", almanacHandler::getAlmanac)
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-client-api -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 5: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-client-api/
|
|
git commit -m "feat: migrate client handlers to client-api module"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: 更新网关路由配置
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-gateway/src/main/java/io/destiny/gateway/config/GatewayConfig.java`
|
|
|
|
**Step 1: 更新网关路由配置**
|
|
|
|
```java
|
|
package io.destiny.gateway.config;
|
|
|
|
import io.destiny.common.exception.ReactiveGlobalExceptionHandler;
|
|
import io.destiny.gateway.handler.HealthHandler;
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
import org.springdoc.core.annotations.RouterOperation;
|
|
import org.springdoc.core.annotations.RouterOperations;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.web.bind.annotation.RequestMethod;
|
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
|
|
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
|
|
|
@Configuration
|
|
public class GatewayConfig {
|
|
|
|
@Bean
|
|
public ReactiveGlobalExceptionHandler reactiveGlobalExceptionHandler() {
|
|
return new ReactiveGlobalExceptionHandler();
|
|
}
|
|
|
|
@Bean
|
|
@RouterOperations({
|
|
@RouterOperation(path = "/health", method = RequestMethod.GET, beanClass = HealthHandler.class, beanMethod = "handleHealth")
|
|
})
|
|
@Operation(summary = "健康检查路由配置")
|
|
@Tag(name = "健康检查", description = "健康检查相关接口")
|
|
public RouterFunction<ServerResponse> healthRouter(HealthHandler healthHandler) {
|
|
return route()
|
|
.GET("/health", healthHandler::handleHealth)
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-gateway -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 3: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-gateway/
|
|
git commit -m "refactor: update gateway routing configuration"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段三:网关配置(1-2天)
|
|
|
|
### Task 7: 实现 JWT 认证工具类
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-common/src/main/java/io/destiny/common/util/JwtUtil.java`
|
|
- Create: `everything-is-suitable-common/src/main/java/io/destiny/common/config/JwtConfig.java`
|
|
|
|
**Step 1: 创建 JWT 工具类**
|
|
|
|
```java
|
|
package io.destiny.common.util;
|
|
|
|
import io.jsonwebtoken.Claims;
|
|
import io.jsonwebtoken.Jwts;
|
|
import io.jsonwebtoken.SignatureAlgorithm;
|
|
import io.jsonwebtoken.security.Keys;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import javax.crypto.SecretKey;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Date;
|
|
|
|
@Component
|
|
public class JwtUtil {
|
|
|
|
@Value("${jwt.secret:everything-is-suitable-secret-key-for-jwt-token-generation}")
|
|
private String secret;
|
|
|
|
@Value("${jwt.expiration:86400000}")
|
|
private long expiration;
|
|
|
|
private SecretKey getSigningKey() {
|
|
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
public String generateToken(String userId, String role) {
|
|
Claims claims = Jwts.claims();
|
|
claims.setSubject(userId);
|
|
claims.put("role", role);
|
|
claims.setIssuedAt(new Date());
|
|
claims.setExpiration(new Date(System.currentTimeMillis() + expiration));
|
|
|
|
return Jwts.builder()
|
|
.setClaims(claims)
|
|
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
|
.compact();
|
|
}
|
|
|
|
public Claims parseToken(String token) {
|
|
return Jwts.parserBuilder()
|
|
.setSigningKey(getSigningKey())
|
|
.build()
|
|
.parseClaimsJws(token)
|
|
.getBody();
|
|
}
|
|
|
|
public boolean validateToken(String token) {
|
|
try {
|
|
parseToken(token);
|
|
return true;
|
|
} catch (Exception e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public String getUserIdFromToken(String token) {
|
|
return parseToken(token).getSubject();
|
|
}
|
|
|
|
public String getRoleFromToken(String token) {
|
|
return parseToken(token).get("role", String.class);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 创建 JWT 配置类**
|
|
|
|
```java
|
|
package io.destiny.common.config;
|
|
|
|
import io.destiny.common.util.JwtUtil;
|
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
@Configuration
|
|
public class JwtConfig {
|
|
|
|
@Bean
|
|
public JwtUtil jwtUtil() {
|
|
return new JwtUtil();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 更新 application.yml 添加 JWT 配置**
|
|
|
|
Modify: `everything-is-suitable-client-app/src/main/resources/application.yml`
|
|
|
|
```yaml
|
|
jwt:
|
|
secret: ${JWT_SECRET:everything-is-suitable-secret-key-for-jwt-token-generation}
|
|
expiration: ${JWT_EXPIRATION:86400000}
|
|
```
|
|
|
|
Modify: `everything-is-suitable-admin-app/src/main/resources/application.yml`
|
|
|
|
```yaml
|
|
jwt:
|
|
secret: ${JWT_SECRET:everything-is-suitable-secret-key-for-jwt-token-generation}
|
|
expiration: ${JWT_EXPIRATION:86400000}
|
|
```
|
|
|
|
**Step 4: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-common -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 5: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-common/
|
|
git commit -m "feat: add JWT utility and configuration"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 8: 实现 JWT 认证过滤器
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-gateway/src/main/java/io/destiny/gateway/filter/JwtAuthenticationFilter.java`
|
|
|
|
**Step 1: 创建 JWT 认证过滤器**
|
|
|
|
```java
|
|
package io.destiny.gateway.filter;
|
|
|
|
import io.destiny.common.util.JwtUtil;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.server.ServerWebExchange;
|
|
import org.springframework.web.server.WebFilter;
|
|
import org.springframework.web.server.WebFilterChain;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class JwtAuthenticationFilter implements WebFilter {
|
|
|
|
private final JwtUtil jwtUtil;
|
|
|
|
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
|
|
this.jwtUtil = jwtUtil;
|
|
}
|
|
|
|
@Override
|
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
|
String path = exchange.getRequest().getPath().value();
|
|
|
|
if (isPublicPath(path)) {
|
|
return chain.filter(exchange);
|
|
}
|
|
|
|
String token = extractToken(exchange.getRequest().getHeaders().getFirst("Authorization"));
|
|
|
|
if (token == null || !jwtUtil.validateToken(token)) {
|
|
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
|
return exchange.getResponse().setComplete();
|
|
}
|
|
|
|
String userId = jwtUtil.getUserIdFromToken(token);
|
|
String role = jwtUtil.getRoleFromToken(token);
|
|
|
|
exchange.getRequest().mutate()
|
|
.header("X-User-Id", userId)
|
|
.header("X-User-Role", role)
|
|
.build();
|
|
|
|
return chain.filter(exchange);
|
|
}
|
|
|
|
private boolean isPublicPath(String path) {
|
|
return path.equals("/health") ||
|
|
path.startsWith("/actuator") ||
|
|
path.startsWith("/api/auth/login") ||
|
|
path.startsWith("/api/auth/register");
|
|
}
|
|
|
|
private String extractToken(String authHeader) {
|
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
|
return authHeader.substring(7);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-gateway -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 3: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-gateway/
|
|
git commit -m "feat: add JWT authentication filter"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 9: 实现后台管理 RBAC 权限控制
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin-api/src/main/java/io/destiny/admin/api/filter/AdminAuthenticationFilter.java`
|
|
- Create: `everything-is-suitable-common/src/main/java/io/destiny/common/annotation/RequirePermission.java`
|
|
- Create: `everything-is-suitable-common/src/main/java/io/destiny/common/annotation/RequireRole.java`
|
|
|
|
**Step 1: 创建权限注解**
|
|
|
|
```java
|
|
package io.destiny.common.annotation;
|
|
|
|
import java.lang.annotation.ElementType;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.lang.annotation.Target;
|
|
|
|
@Target(ElementType.METHOD)
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
public @interface RequirePermission {
|
|
String value();
|
|
}
|
|
```
|
|
|
|
```java
|
|
package io.destiny.common.annotation;
|
|
|
|
import java.lang.annotation.ElementType;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.lang.annotation.Target;
|
|
|
|
@Target(ElementType.METHOD)
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
public @interface RequireRole {
|
|
String[] value();
|
|
}
|
|
```
|
|
|
|
**Step 2: 创建后台管理认证过滤器**
|
|
|
|
```java
|
|
package io.destiny.admin.api.filter;
|
|
|
|
import io.destiny.common.util.JwtUtil;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.server.ServerWebExchange;
|
|
import org.springframework.web.server.WebFilter;
|
|
import org.springframework.web.server.WebFilterChain;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@Component
|
|
public class AdminAuthenticationFilter implements WebFilter {
|
|
|
|
private final JwtUtil jwtUtil;
|
|
|
|
public AdminAuthenticationFilter(JwtUtil jwtUtil) {
|
|
this.jwtUtil = jwtUtil;
|
|
}
|
|
|
|
@Override
|
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
|
String path = exchange.getRequest().getPath().value();
|
|
|
|
if (isPublicPath(path)) {
|
|
return chain.filter(exchange);
|
|
}
|
|
|
|
String token = extractToken(exchange.getRequest().getHeaders().getFirst("Authorization"));
|
|
|
|
if (token == null || !jwtUtil.validateToken(token)) {
|
|
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
|
return exchange.getResponse().setComplete();
|
|
}
|
|
|
|
String userId = jwtUtil.getUserIdFromToken(token);
|
|
String role = jwtUtil.getRoleFromToken(token);
|
|
|
|
if (!isAdminRole(role)) {
|
|
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
|
|
return exchange.getResponse().setComplete();
|
|
}
|
|
|
|
exchange.getRequest().mutate()
|
|
.header("X-User-Id", userId)
|
|
.header("X-User-Role", role)
|
|
.build();
|
|
|
|
return chain.filter(exchange);
|
|
}
|
|
|
|
private boolean isPublicPath(String path) {
|
|
return path.equals("/health") ||
|
|
path.startsWith("/actuator") ||
|
|
path.startsWith("/api/auth/login");
|
|
}
|
|
|
|
private boolean isAdminRole(String role) {
|
|
return "admin".equals(role) || "super_admin".equals(role);
|
|
}
|
|
|
|
private String extractToken(String authHeader) {
|
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
|
return authHeader.substring(7);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 编译验证**
|
|
|
|
Run: `mvn clean compile -pl everything-is-suitable-admin-api,everything-is-suitable-common -am`
|
|
|
|
Expected: BUILD SUCCESS
|
|
|
|
**Step 4: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin-api/ everything-is-suitable-common/
|
|
git commit -m "feat: add RBAC permission control for admin"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段四:配置和部署(1-2天)
|
|
|
|
### Task 10: 创建 Docker 部署文件
|
|
|
|
**Files:**
|
|
- Create: `Dockerfile.client`
|
|
- Create: `Dockerfile.admin`
|
|
- Create: `docker-compose.yml`
|
|
|
|
**Step 1: 创建客户端 Dockerfile**
|
|
|
|
```dockerfile
|
|
FROM eclipse-temurin:21-jre-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
COPY everything-is-suitable-client-app/target/everything-is-suitable-client-app-1.0.0.jar app.jar
|
|
|
|
EXPOSE 8081
|
|
|
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
```
|
|
|
|
**Step 2: 创建后台管理 Dockerfile**
|
|
|
|
```dockerfile
|
|
FROM eclipse-temurin:21-jre-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
COPY everything-is-suitable-admin-app/target/everything-is-suitable-admin-app-1.0.0.jar app.jar
|
|
|
|
EXPOSE 8082
|
|
|
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
```
|
|
|
|
**Step 3: 创建 docker-compose.yml**
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
environment:
|
|
POSTGRES_DB: everything_suitable
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
|
|
redis:
|
|
image: redis:7
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
|
|
client-app:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.client
|
|
ports:
|
|
- "8081:8081"
|
|
environment:
|
|
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/everything_suitable
|
|
SPRING_R2DBC_USERNAME: postgres
|
|
SPRING_R2DBC_PASSWORD: postgres
|
|
depends_on:
|
|
- postgres
|
|
- redis
|
|
|
|
admin-app:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.admin
|
|
ports:
|
|
- "8082:8082"
|
|
environment:
|
|
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/everything_suitable
|
|
SPRING_R2DBC_USERNAME: postgres
|
|
SPRING_R2DBC_PASSWORD: postgres
|
|
depends_on:
|
|
- postgres
|
|
- redis
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
```
|
|
|
|
**Step 4: 提交**
|
|
|
|
```bash
|
|
git add Dockerfile.client Dockerfile.admin docker-compose.yml
|
|
git commit -m "feat: add Docker deployment files"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 11: 创建部署脚本
|
|
|
|
**Files:**
|
|
- Create: `deploy.sh`
|
|
|
|
**Step 1: 创建部署脚本**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
echo "=== 开始部署 ==="
|
|
|
|
# 构建客户端应用
|
|
echo "构建客户端应用..."
|
|
cd everything-is-suitable-client-app
|
|
mvn clean package -DskipTests
|
|
cd ..
|
|
|
|
# 构建后台管理应用
|
|
echo "构建后台管理应用..."
|
|
cd everything-is-suitable-admin-app
|
|
mvn clean package -DskipTests
|
|
cd ..
|
|
|
|
# 构建 Docker 镜像
|
|
echo "构建 Docker 镜像..."
|
|
docker build -f Dockerfile.client -t client-app:latest .
|
|
docker build -f Dockerfile.admin -t admin-app:latest .
|
|
|
|
# 启动服务
|
|
echo "启动服务..."
|
|
docker-compose up -d
|
|
|
|
echo "=== 部署完成 ==="
|
|
echo "客户端应用: http://localhost:8081"
|
|
echo "后台管理应用: http://localhost:8082"
|
|
echo "健康检查: http://localhost:8081/actuator/health"
|
|
```
|
|
|
|
**Step 2: 添加执行权限**
|
|
|
|
Run: `chmod +x deploy.sh`
|
|
|
|
Expected: 脚本获得执行权限
|
|
|
|
**Step 3: 提交**
|
|
|
|
```bash
|
|
git add deploy.sh
|
|
git commit -m "feat: add deployment script"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段五:测试和优化(2-3天)
|
|
|
|
### Task 12: 编写集成测试
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-client-app/src/test/java/io/destiny/client/ClientApplicationIntegrationTest.java`
|
|
- Create: `everything-is-suitable-admin-app/src/test/java/io/destiny/admin/AdminApplicationIntegrationTest.java`
|
|
|
|
**Step 1: 创建客户端应用集成测试**
|
|
|
|
```java
|
|
package io.destiny.client;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
|
|
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
class ClientApplicationIntegrationTest {
|
|
|
|
@Autowired
|
|
private WebTestClient webTestClient;
|
|
|
|
@Test
|
|
void contextLoads() {
|
|
}
|
|
|
|
@Test
|
|
void healthCheck() {
|
|
webTestClient.get()
|
|
.uri("/actuator/health")
|
|
.exchange()
|
|
.expectStatus().isOk();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 创建后台管理应用集成测试**
|
|
|
|
```java
|
|
package io.destiny.admin;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
|
|
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
class AdminApplicationIntegrationTest {
|
|
|
|
@Autowired
|
|
private WebTestClient webTestClient;
|
|
|
|
@Test
|
|
void contextLoads() {
|
|
}
|
|
|
|
@Test
|
|
void healthCheck() {
|
|
webTestClient.get()
|
|
.uri("/actuator/health")
|
|
.exchange()
|
|
.expectStatus().isOk();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 运行测试**
|
|
|
|
Run: `mvn test -pl everything-is-suitable-client-app,everything-is-suitable-admin-app`
|
|
|
|
Expected: 测试通过
|
|
|
|
**Step 4: 提交**
|
|
|
|
```bash
|
|
git add everything-is-suitable-client-app/ everything-is-suitable-admin-app/
|
|
git commit -m "test: add integration tests"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 13: 性能测试和优化
|
|
|
|
**Files:**
|
|
- Create: `performance-test.sh`
|
|
|
|
**Step 1: 创建性能测试脚本**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
echo "=== 性能测试 ==="
|
|
|
|
# 客户端应用性能测试
|
|
echo "测试客户端应用..."
|
|
ab -n 1000 -c 10 http://localhost:8081/actuator/health
|
|
|
|
# 后台管理应用性能测试
|
|
echo "测试后台管理应用..."
|
|
ab -n 1000 -c 10 http://localhost:8082/actuator/health
|
|
|
|
echo "=== 性能测试完成 ==="
|
|
```
|
|
|
|
**Step 2: 提交**
|
|
|
|
```bash
|
|
git add performance-test.sh
|
|
git commit -m "test: add performance test script"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 14: 文档更新
|
|
|
|
**Files:**
|
|
- Create: `docs/deployment-guide.md`
|
|
- Create: `docs/api-documentation.md`
|
|
|
|
**Step 1: 创建部署指南**
|
|
|
|
```markdown
|
|
# 部署指南
|
|
|
|
## 环境要求
|
|
|
|
- JDK 21
|
|
- Maven 3.9+
|
|
- Docker 20.10+
|
|
- Docker Compose 2.0+
|
|
|
|
## 本地开发
|
|
|
|
### 启动数据库
|
|
|
|
```bash
|
|
docker-compose up -d postgres redis
|
|
```
|
|
|
|
### 启动客户端应用
|
|
|
|
```bash
|
|
cd everything-is-suitable-client-app
|
|
mvn spring-boot:run
|
|
```
|
|
|
|
### 启动后台管理应用
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin-app
|
|
mvn spring-boot:run
|
|
```
|
|
|
|
## 生产部署
|
|
|
|
### 使用 Docker Compose 部署
|
|
|
|
```bash
|
|
./deploy.sh
|
|
```
|
|
|
|
### 手动部署
|
|
|
|
1. 构建应用
|
|
```bash
|
|
mvn clean package -DskipTests
|
|
```
|
|
|
|
2. 构建 Docker 镜像
|
|
```bash
|
|
docker build -f Dockerfile.client -t client-app:latest .
|
|
docker build -f Dockerfile.admin -t admin-app:latest .
|
|
```
|
|
|
|
3. 启动服务
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
## 监控
|
|
|
|
- 健康检查: http://localhost:8081/actuator/health
|
|
- 应用信息: http://localhost:8081/actuator/info
|
|
- 指标: http://localhost:8081/actuator/metrics
|
|
```
|
|
|
|
**Step 2: 创建 API 文档**
|
|
|
|
```markdown
|
|
# API 文档
|
|
|
|
## 客户端 API
|
|
|
|
### 认证接口
|
|
|
|
#### 登录
|
|
```
|
|
POST /api/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "user",
|
|
"password": "password"
|
|
}
|
|
```
|
|
|
|
#### 注册
|
|
```
|
|
POST /api/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "user",
|
|
"password": "password",
|
|
"email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
### 命理接口
|
|
|
|
#### 获取每日运势
|
|
```
|
|
GET /api/fortune/daily?userId=123&date=2024-01-01
|
|
```
|
|
|
|
#### 获取黄历
|
|
```
|
|
POST /api/almanac
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"date": "2024-01-01"
|
|
}
|
|
```
|
|
|
|
## 后台管理 API
|
|
|
|
### 用户管理
|
|
|
|
#### 获取用户列表
|
|
```
|
|
GET /api/users
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
#### 创建用户
|
|
```
|
|
POST /api/users
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "admin",
|
|
"password": "password",
|
|
"role": "admin"
|
|
}
|
|
```
|
|
|
|
### 统计分析
|
|
|
|
#### 获取统计数据
|
|
```
|
|
GET /api/statistics
|
|
Authorization: Bearer <token>
|
|
```
|
|
```
|
|
|
|
**Step 3: 提交**
|
|
|
|
```bash
|
|
git add docs/
|
|
git commit -m "docs: add deployment guide and API documentation"
|
|
```
|
|
|
|
---
|
|
|
|
## 验收标准
|
|
|
|
### 功能验收
|
|
|
|
- [ ] 客户端应用可以独立启动和运行
|
|
- [ ] 后台管理应用可以独立启动和运行
|
|
- [ ] 客户端和后台管理共享业务逻辑层
|
|
- [ ] JWT 认证正常工作
|
|
- [ ] 后台管理 RBAC 权限控制正常工作
|
|
- [ ] Actuator 监控端点可访问
|
|
|
|
### 性能验收
|
|
|
|
- [ ] 应用启动时间 < 30 秒
|
|
- [ ] API 响应时间 P95 < 1 秒
|
|
- [ ] 并发 100 请求/秒无错误
|
|
|
|
### 安全验收
|
|
|
|
- [ ] 所有接口需要认证(除公开接口)
|
|
- [ ] JWT Token 有效期正确
|
|
- [ ] 敏感数据已脱敏
|
|
- [ ] SQL 注入防护有效
|
|
|
|
### 部署验收
|
|
|
|
- [ ] Docker 镜像构建成功
|
|
- [ ] docker-compose 启动成功
|
|
- [ ] 所有服务健康检查通过
|
|
- [ ] 日志正常输出
|
|
|
|
---
|
|
|
|
## 总结
|
|
|
|
本实施计划将现有单体应用重构为双应用架构,实现:
|
|
|
|
1. **独立部署**: 客户端和后台管理应用可以独立部署到不同服务器
|
|
2. **业务复用**: domain 和 service 层共用,避免代码重复
|
|
3. **网关路由**: 通过网关区分 /client/** 和 /admin/** 请求
|
|
4. **安全认证**: JWT 认证 + RBAC 权限控制
|
|
5. **监控运维**: 基于 Actuator 的健康检查和指标监控
|
|
|
|
**预计时间**: 7-12 天
|
|
|
|
**风险提示**:
|
|
- 模块拆分可能影响现有功能,需要充分测试
|
|
- JWT 配置需要确保密钥安全
|
|
- 数据库连接池配置需要根据实际负载调整
|