# 双应用架构重构实施计划 > **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 配置需要确保密钥安全 - 数据库连接池配置需要根据实际负载调整