# 双应用架构重构实施计划
> **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
4.0.0
io.destiny
everything-is-suitable-api
1.0.0
everything-is-suitable-client-app
jar
Everything Is Suitable Client App
Client application for Everything Is Suitable API
io.destiny
everything-is-suitable-client-api
${project.version}
io.destiny
everything-is-suitable-biz
${project.version}
io.destiny
everything-is-suitable-db
${project.version}
io.destiny
everything-is-suitable-common
${project.version}
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-starter-actuator
io.micrometer
micrometer-registry-prometheus
org.postgresql
r2dbc-postgresql
org.postgresql
postgresql
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
io.destiny.client.ClientApplication
```
**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
everything-is-suitable-client-app
everything-is-suitable-admin-app
everything-is-suitable-app
everything-is-suitable-common
everything-is-suitable-db
everything-is-suitable-sys
everything-is-suitable-biz
everything-is-suitable-gateway
everything-is-suitable-client
everything-is-suitable-statistics
everything-is-suitable-client-api
everything-is-suitable-admin-api
```
**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
4.0.0
io.destiny
everything-is-suitable-api
1.0.0
everything-is-suitable-admin-app
jar
Everything Is Suitable Admin App
Admin application for Everything Is Suitable API
io.destiny
everything-is-suitable-admin-api
${project.version}
io.destiny
everything-is-suitable-biz
${project.version}
io.destiny
everything-is-suitable-sys
${project.version}
io.destiny
everything-is-suitable-statistics
${project.version}
io.destiny
everything-is-suitable-db
${project.version}
io.destiny
everything-is-suitable-common
${project.version}
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-starter-actuator
io.micrometer
micrometer-registry-prometheus
org.postgresql
r2dbc-postgresql
org.postgresql
postgresql
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
io.destiny.admin.AdminApplication
```
**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
4.0.0
io.destiny
everything-is-suitable-api
1.0.0
everything-is-suitable-client-api
jar
Everything Is Suitable Client API
Client API layer for Everything Is Suitable API
io.destiny
everything-is-suitable-biz
${project.version}
io.destiny
everything-is-suitable-common
${project.version}
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-starter-validation
org.springdoc
springdoc-openapi-starter-webflux-ui
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
true
```
**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 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 login(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("{\"message\":\"Login endpoint - to be implemented\"}");
}
public Mono 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 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 getMonthlyFortune(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("{\"message\":\"Monthly fortune - to be implemented\"}");
}
public Mono 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
4.0.0
io.destiny
everything-is-suitable-api
1.0.0
everything-is-suitable-admin-api
jar
Everything Is Suitable Admin API
Admin API layer for Everything Is Suitable API
io.destiny
everything-is-suitable-biz
${project.version}
io.destiny
everything-is-suitable-sys
${project.version}
io.destiny
everything-is-suitable-statistics
${project.version}
io.destiny
everything-is-suitable-common
${project.version}
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-starter-validation
org.springdoc
springdoc-openapi-starter-webflux-ui
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
true
```
**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 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 listUsers(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("{\"message\":\"List users - to be implemented\"}");
}
public Mono 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 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 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 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 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 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 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
```
#### 创建用户
```
POST /api/users
Authorization: Bearer
Content-Type: application/json
{
"username": "admin",
"password": "password",
"role": "admin"
}
```
### 统计分析
#### 获取统计数据
```
GET /api/statistics
Authorization: Bearer
```
```
**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 配置需要确保密钥安全
- 数据库连接池配置需要根据实际负载调整