Files
everything-is-suitable/everything-is-suitable-api/docs/plans/2025-02-24-dual-app-architecture-refactor.md
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

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

手动部署

  1. 构建应用
mvn clean package -DskipTests
  1. 构建 Docker 镜像
docker build -f Dockerfile.client -t client-app:latest .
docker build -f Dockerfile.admin -t admin-app:latest .
  1. 启动服务
docker-compose up -d

监控


**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 启动成功
  • 所有服务健康检查通过
  • 日志正常输出

总结

本实施计划将现有单体应用重构为双应用架构,实现:

  1. 独立部署: 客户端和后台管理应用可以独立部署到不同服务器
  2. 业务复用: domain 和 service 层共用,避免代码重复
  3. 网关路由: 通过网关区分 /client/** 和 /admin/** 请求
  4. 安全认证: JWT 认证 + RBAC 权限控制
  5. 监控运维: 基于 Actuator 的健康检查和指标监控

预计时间: 7-12 天

风险提示:

  • 模块拆分可能影响现有功能,需要充分测试
  • JWT 配置需要确保密钥安全
  • 数据库连接池配置需要根据实际负载调整