添加用户管理视图、API和状态管理文件
47 KiB
双应用架构重构实施计划
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 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: 创建客户端应用启动类
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: 创建客户端应用配置文件
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 添加新模块
<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: 提交
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 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: 创建后台管理应用启动类
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: 创建后台管理应用配置文件
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: 提交
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 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: 创建客户端路由配置
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: 创建客户端认证处理器
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: 创建客户端命理处理器
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: 提交
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 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: 创建后台管理路由配置
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: 创建后台管理用户处理器
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: 创建后台管理统计处理器
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: 提交
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: 创建客户端黄历处理器
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
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: 提交
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: 更新网关路由配置
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: 提交
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 工具类
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 配置类
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
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
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: 提交
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 认证过滤器
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: 提交
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: 创建权限注解
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();
}
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: 创建后台管理认证过滤器
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: 提交
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
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
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
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: 提交
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: 创建部署脚本
#!/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: 提交
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: 创建客户端应用集成测试
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: 创建后台管理应用集成测试
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: 提交
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: 创建性能测试脚本
#!/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: 提交
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: 创建部署指南
# 部署指南
## 环境要求
- JDK 21
- Maven 3.9+
- Docker 20.10+
- Docker Compose 2.0+
## 本地开发
### 启动数据库
```bash
docker-compose up -d postgres redis
启动客户端应用
cd everything-is-suitable-client-app
mvn spring-boot:run
启动后台管理应用
cd everything-is-suitable-admin-app
mvn spring-boot:run
生产部署
使用 Docker Compose 部署
./deploy.sh
手动部署
- 构建应用
mvn clean package -DskipTests
- 构建 Docker 镜像
docker build -f Dockerfile.client -t client-app:latest .
docker build -f Dockerfile.admin -t admin-app:latest .
- 启动服务
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
#### 创建用户
POST /api/users Authorization: Bearer Content-Type: application/json
{ "username": "admin", "password": "password", "role": "admin" }
### 统计分析
#### 获取统计数据
GET /api/statistics Authorization: Bearer
Step 3: 提交
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 启动成功
- 所有服务健康检查通过
- 日志正常输出
总结
本实施计划将现有单体应用重构为双应用架构,实现:
- 独立部署: 客户端和后台管理应用可以独立部署到不同服务器
- 业务复用: domain 和 service 层共用,避免代码重复
- 网关路由: 通过网关区分 /client/** 和 /admin/** 请求
- 安全认证: JWT 认证 + RBAC 权限控制
- 监控运维: 基于 Actuator 的健康检查和指标监控
预计时间: 7-12 天
风险提示:
- 模块拆分可能影响现有功能,需要充分测试
- JWT 配置需要确保密钥安全
- 数据库连接池配置需要根据实际负载调整