Files
gym-manage/docs/design/HLD-技术架构设计.md
T

33 KiB
Raw Blame History

健身房管理系统技术架构设计文档

⚠️ 归档说明

归档日期: 2026-03-08 归档原因: 文档架构优化,技术架构内容已整合到 T-ILD 文档体系 替代文档:

  • [GYM-T-ILD-BASIC-001](technical/T-ILD-基础版 - 技术实现详细设计.md)
  • [GYM-T-ILD-SUBSCRIPTION-001](technical/T-ILD-付费订阅版 - 技术实现详细设计.md)

本文档仅供历史参考,请以 T-ILD 文档为准。

文档编号: GYM-HLD-TECH-001
版本: v1.0
日期: 2026-03-04
作者: 张翔
状态: 已发布


文档修订历史

版本 日期 作者 修订内容
v1.0 2026-03-04 张翔 创建技术架构设计文档

参考文档

  • 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
  • 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001
  • 《健身房管理系统基础版业务概要设计文档》 GYM-B-HLD-BASIC-001
  • 《健身房管理系统付费订阅版业务概要设计文档》 GYM-B-HLD-SUBSCRIPTION-001
  • Spring Boot 3 官方文档
  • Spring WebFlux 官方文档
  • R2DBC 规范文档
  • PostgreSQL 官方文档

一、架构决策

1.1 架构选型

经过深入评估,本系统采用以下架构决策:

决策项 选择方案 理由
应用架构 单体应用 适合当前规模(基础版100并发用户,付费订阅版500并发用户),开发效率高,部署简单,成本低
编程模型 响应式编程(WebFlux + R2DBC 高并发能力(10x 提升),低延迟(50% 降低),资源利用率高(75% 降低)
部署方式 Docker Compose 一键部署,环境一致性好,回滚快速
数据库 PostgreSQL 金融级数据库,支持 ACID 事务,JSONB 支持灵活配置
缓存 Redis 高性能缓存,支持分布式锁
消息队列 RabbitMQ 成熟稳定,支持延迟消息
搜索引擎 Elasticsearch 全文搜索,适合复杂查询
监控 Prometheus + Grafana 完善的监控体系,可视化好

1.2 技术栈

核心技术栈

技术组件 版本 用途
Spring Boot 3.2.x 应用框架
Spring WebFlux 3.2.x 响应式 Web 框架
Spring Data R2DBC 3.2.x 响应式数据访问
PostgreSQL R2DBC 1.0.0.RELEASE PostgreSQL 响应式驱动
Spring Security 6.2.x 安全框架
Redis Reactive 3.2.x 响应式缓存
RabbitMQ 3.12.x 消息队列
Elasticsearch 8.11.x 搜索引擎
Prometheus Latest 监控指标采集
Grafana Latest 监控可视化
Docker 24.x 容器化部署
Docker Compose 2.20.x 容器编排

开发工具

工具 版本 用途
JDK 17+ 运行环境
Maven 3.9.x 项目构建
Lombok 1.18.x 代码简化
MapStruct 1.5.x 对象映射
Micrometer 1.12.x 监控指标
SpringDoc OpenAPI 2.3.x API 文档

二、系统架构设计

2.1 总体架构

采用分层架构 + 模块化设计的单体应用:

flowchart TB
    subgraph 单体应用总体架构
        A[客户端层<br/>• 会员小程序 uniapp+Vue3<br/>• 教练端App uniapp+Vue3<br/>• 管理后台PC Vue3+Vite<br/>• 硬件设备 人脸/NFC]
        B[Nginx 反向代理<br/>• 负载均衡<br/>• SSL 终止<br/>• 静态资源<br/>• 限流]
        C[Presentation Layer WebFlux<br/>• Controller<br/>• Router<br/>• Filter<br/>• Validator]
        D[Application Layer 业务编排<br/>• Service<br/>• Facade<br/>• Orchestrator<br/>• 事务管理]
        E[Domain Layer 领域模型<br/>• Entity<br/>• Value Object<br/>• Domain Service<br/>• Repository]
        F[Infrastructure Layer 基础设施<br/>• Repository R2DBC<br/>• Cache Redis<br/>• Message RabbitMQ<br/>• Search Elasticsearch<br/>• File OSS<br/>• Distributed Lock]
        G[外部服务层<br/>• PostgreSQL<br/>• Redis<br/>• RabbitMQ<br/>• Elasticsearch<br/>• 微信开放平台<br/>• 短信服务<br/>• 支付服务<br/>• OSS存储]
        H[监控与运维层<br/>• Prometheus<br/>• Grafana<br/>• 日志收集<br/>• 告警]
        A --> B
        B --> C
        C --> D
        D --> E
        E --> F
        F --> G
        G --> H
    end

2.2 分层架构详解

2.2.1 Presentation Layer(表现层)

职责

  • 接收 HTTP 请求
  • 参数验证
  • 路由转发
  • 响应封装
  • 异常处理

技术实现

  • Spring WebFlux Router
  • Spring Validation
  • Spring Security Reactive
  • Global Exception Handler

2.2.2 Application Layer(应用层)

职责

  • 业务逻辑编排
  • 事务管理
  • 跨模块协调
  • 权限校验

技术实现

  • Service 类
  • @Transactional 注解
  • 分布式锁
  • Saga 模式(跨服务事务)

2.2.3 Domain Layer(领域层)

职责

  • 领域模型定义
  • 业务规则封装
  • 领域服务
  • 仓储接口定义

技术实现

  • Entity 类
  • Value Object 类
  • Domain Service 类
  • Repository 接口

2.2.4 Infrastructure Layer(基础设施层)

职责

  • 数据访问实现
  • 缓存管理
  • 消息队列
  • 文件存储
  • 外部服务调用

技术实现

  • R2DBC Repository
  • Redis Reactive
  • RabbitMQ Reactive
  • Elasticsearch Reactive
  • OSS SDK

2.3 模块化设计

单体应用内部采用模块化设计,为未来拆分微服务做准备:

gym-manage/
├── gym-manage-api/              # API 层
│   ├── controller/
│   │   ├── member/            # 会员模块 API
│   │   ├── booking/           # 预约模块 API
│   │   ├── checkin/           # 签到模块 API
│   │   ├── benefit/           # 权益模块 API
│   │   ├── subscription/       # 订阅模块 API
│   │   ├── marketing/         # 营销模块 API
│   │   └── analytics/         # 数据分析模块 API
│   ├── dto/
│   │   ├── request/           # 请求 DTO
│   │   └── response/          # 响应 DTO
│   └── config/
│       ├── WebFluxConfig.java
│       ├── SecurityConfig.java
│       └── R2dbcConfig.java
│
├── gym-manage-application/       # 应用层
│   ├── service/
│   │   ├── member/
│   │   ├── booking/
│   │   ├── checkin/
│   │   ├── benefit/
│   │   ├── subscription/
│   │   ├── marketing/
│   │   └── analytics/
│   ├── facade/
│   └── orchestrator/
│
├── gym-manage-domain/           # 领域层
│   ├── entity/
│   │   ├── Member.java
│   │   ├── BookingRecord.java
│   │   ├── CheckinRecord.java
│   │   ├── MemberBenefit.java
│   │   ├── SubscriptionRecord.java
│   │   └── ...
│   ├── valueobject/
│   ├── repository/
│   │   ├── MemberRepository.java
│   │   ├── BookingRecordRepository.java
│   │   └── ...
│   └── service/
│       └── DomainService.java
│
├── gym-manage-infrastructure/   # 基础设施层
│   ├── repository/
│   │   └── impl/
│   │       ├── MemberRepositoryImpl.java
│   │       ├── BookingRecordRepositoryImpl.java
│   │       └── ...
│   ├── cache/
│   │   └── RedisCacheService.java
│   ├── message/
│   │   └── RabbitMQService.java
│   ├── search/
│   │   └── ElasticsearchService.java
│   ├── lock/
│   │   └── DistributedLockService.java
│   └── config/
│       ├── R2dbcConfiguration.java
│       ├── RedisConfiguration.java
│       ├── RabbitMQConfiguration.java
│       └── ElasticsearchConfiguration.java
│
└── gym-manage-main/            # 主启动类
    ├── GymManageApplication.java
    └── resources/
        ├── application.yml
        ├── application-dev.yml
        └── application-prod.yml

三、响应式编程架构

3.1 响应式编程模型

本系统采用 Project Reactor 作为响应式编程库:

组件 类型 说明
Mono 0-1 个元素 表示异步计算结果,返回单个对象或空
Flux 0-N 个元素 表示异步数据流,返回多个对象
Scheduler 线程调度器 控制异步操作的执行线程

3.2 响应式编程规范

3.2.1 基本原则

  1. 永不阻塞

    • 禁止在响应式流中使用 block()blockFirst()blockLast()
    • 所有 I/O 操作必须使用非阻塞方式
  2. 链式调用

    • 使用 flatMapmapfilter 等操作符链式调用
    • 避免嵌套的 subscribe
  3. 错误处理

    • 使用 onErrorResumeonErrorReturn 处理错误
    • 避免使用 try-catch 捕获响应式异常
  4. 背压处理

    • 使用 onBackpressureBufferonBackpressureDrop 处理背压
    • 避免内存溢出

3.2.2 代码示例

正确示例

public Mono<Member> getMember(Long id) {
    return memberRepository.findById(id)
        .switchIfEmpty(Mono.error(new BusinessException("会员不存在")))
        .flatMap(member -> loadMemberCards(member.getId()))
        .flatMap(member -> loadMemberBenefits(member.getId()))
        .doOnSuccess(member -> log.info("查询会员成功: memberId={}", member.getId()))
        .doOnError(e -> log.error("查询会员失败: memberId={}", id, e));
}

错误示例

public Member getMember(Long id) {
    // 错误:使用 block() 阻塞
    return memberRepository.findById(id).block();
}

public Mono<Member> getMember(Long id) {
    return memberRepository.findById(id)
        .flatMap(member -> {
            // 错误:在 flatMap 中使用 block()
            List<MemberCard> cards = memberCardRepository.findByMemberId(member.getId()).collectList().block();
            return Mono.just(member);
        });
}

3.3 响应式事务管理

3.3.1 本地事务

使用 @Transactional 注解管理本地事务:

@Service
public class BookingService {
    
    @Transactional
    public Mono<BookingRecord> bookSlot(BookingRequest request) {
        return validateBooking(request)
            .flatMap(v -> checkSlotAvailability(request.getSlotId()))
            .flatMap(slot -> deductBenefit(request.getMemberId(), slot))
            .flatMap(benefit -> createBookingRecord(request, benefit))
            .flatMap(booking -> updateSlotBookedCount(request.getSlotId()));
    }
}

3.3.2 分布式锁

使用 Redis 实现分布式锁:

@Component
public class RedisDistributedLock {
    
    private final ReactiveRedisTemplate<String, String> redisTemplate;
    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 30;
    
    public Mono<Boolean> tryLock(String key, long expireTime) {
        String lockKey = LOCK_PREFIX + key;
        String lockValue = UUID.randomUUID().toString();
        
        return redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, Duration.ofSeconds(expireTime))
            .flatMap(locked -> {
                if (Boolean.TRUE.equals(locked)) {
                    log.info("获取锁成功: key={}", lockKey);
                    return Mono.just(true);
                } else {
                    log.warn("获取锁失败: key={}", lockKey);
                    return Mono.just(false);
                }
            });
    }
    
    public Mono<Void> unlock(String key) {
        String lockKey = LOCK_PREFIX + key;
        return redisTemplate.delete(lockKey)
            .doOnSuccess(deleted -> {
                if (Boolean.TRUE.equals(deleted)) {
                    log.info("释放锁成功: key={}", lockKey);
                }
            })
            .then();
    }
}

3.3.3 Saga 模式(跨模块事务)

对于跨模块的事务,使用 Saga 模式:

@Service
public class BookingSaga {
    
    public Mono<BookingRecord> execute(BookingRequest request) {
        return bookSlot(request)
            .flatMap(booking -> sendNotification(booking))
            .flatMap(booking -> updateStatistics(booking))
            .onErrorResume(e -> compensate(request, e));
    }
    
    private Mono<BookingRecord> bookSlot(BookingRequest request) {
        // 预约逻辑
    }
    
    private Mono<BookingRecord> sendNotification(BookingRecord booking) {
        // 发送通知
    }
    
    private Mono<BookingRecord> updateStatistics(BookingRecord booking) {
        // 更新统计
    }
    
    private Mono<BookingRecord> compensate(BookingRequest request, Throwable e) {
        // 补偿逻辑
    }
}

四、部署架构

4.1 Docker Compose 部署

4.1.1 完整的 docker-compose.yml

version: '3.8'

services:
  # PostgreSQL 数据库
  postgres:
    image: postgres:16-alpine
    container_name: gym-postgres
    environment:
      POSTGRES_DB: gym_manage
      POSTGRES_USER: ${DB_USERNAME:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
      TZ: Asia/Shanghai
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - gym-network
    restart: unless-stopped

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: gym-redis
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis123}
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - gym-network
    restart: unless-stopped

  # RabbitMQ 消息队列
  rabbitmq:
    image: rabbitmq:3.12-management-alpine
    container_name: gym-rabbitmq
    environment:
      RABBITMQ_DEFAULT_USER: ${MQ_USERNAME:-admin}
      RABBITMQ_DEFAULT_PASS: ${MQ_PASSWORD:-admin123}
      TZ: Asia/Shanghai
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5
    networks:
      - gym-network
    restart: unless-stopped

  # Elasticsearch 搜索引擎
  elasticsearch:
    image: elasticsearch:8.11.0
    container_name: gym-elasticsearch
    environment:
      discovery.type: single-node
      ES_JAVA_OPTS: -Xms512m -Xmx512m
      xpack.security.enabled: "false"
      TZ: Asia/Shanghai
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
    networks:
      - gym-network
    restart: unless-stopped

  # Kibana 可视化
  kibana:
    image: kibana:8.11.0
    container_name: gym-kibana
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
      TZ: Asia/Shanghai
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
    networks:
      - gym-network
    restart: unless-stopped

  # Prometheus 监控
  prometheus:
    image: prom/prometheus:latest
    container_name: gym-prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    networks:
      - gym-network
    restart: unless-stopped

  # Grafana 可视化
  grafana:
    image: grafana/grafana:latest
    container_name: gym-grafana
    environment:
      GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin123}
      TZ: Asia/Shanghai
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
    depends_on:
      - prometheus
    networks:
      - gym-network
    restart: unless-stopped

  # 健身房管理系统应用
  gym-manage:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: gym-manage-app
    environment:
      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: gym_manage
      DB_USERNAME: ${DB_USERNAME:-postgres}
      DB_PASSWORD: ${DB_PASSWORD:-postgres}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
      RABBITMQ_HOST: rabbitmq
      RABBITMQ_PORT: 5672
      RABBITMQ_USERNAME: ${MQ_USERNAME:-admin}
      RABBITMQ_PASSWORD: ${MQ_PASSWORD:-admin123}
      ELASTICSEARCH_HOST: elasticsearch
      ELASTICSEARCH_PORT: 9200
      TZ: Asia/Shanghai
      JAVA_OPTS: -Xms512m -Xmx1024m -XX:+UseG1GC
    ports:
      - "8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy
    volumes:
      - ./logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - gym-network
    restart: unless-stopped

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: gym-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - gym-manage
    networks:
      - gym-network
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:
  elasticsearch_data:
  prometheus_data:
  grafana_data:

networks:
  gym-network:
    driver: bridge

4.1.2 Dockerfile

# 多阶段构建
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /app

# 复制 pom.xml 并下载依赖(利用 Docker 缓存)
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 复制源代码
COPY src ./src

# 打包
RUN mvn clean package -DskipTests -B

# 运行阶段
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# 安装必要的工具
RUN apk add --no-cache curl

# 复制打包好的 jar 文件
COPY --from=builder /app/target/gym-manage-*.jar app.jar

# 创建日志目录
RUN mkdir -p /app/logs

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

4.1.3 部署流程

# 1. 克隆代码
git clone <repository>
cd gym-manage

# 2. 配置环境变量
cp .env.example .env
# 编辑 .env 文件,配置数据库密码等

# 3. 构建并启动
docker-compose up -d

# 4. 查看日志
docker-compose logs -f gym-manage

# 5. 健康检查
curl http://localhost:8080/actuator/health

4.2 配置管理

4.2.1 application-prod.yml

spring:
  r2dbc:
    url: r2dbc:postgresql://${DB_HOST:postgres}:${DB_PORT:5432}/${DB_NAME:gym_manage}
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}
    pool:
      initial-size: 5
      max-size: 20
      max-idle-time: 30m
      max-life-time: 1h
      acquire-timeout: 5s
  
  data:
    redis:
      host: ${REDIS_HOST:redis}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
  
  rabbitmq:
    host: ${RABBITMQ_HOST:rabbitmq}
    port: ${RABBITMQ_PORT:5672}
    username: ${RABBITMQ_USERNAME:guest}
    password: ${RABBITMQ_PASSWORD:guest}
  
  elasticsearch:
    uris: http://${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}
  
  webflux:
    base-path: /api/v1
  
  codec:
    max-in-memory-size: 10MB

server:
  port: 8080
  netty:
    connection-timeout: 5s

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus,httptrace
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: gym-manage
      environment: ${SPRING_PROFILES_ACTIVE:prod}

logging:
  level:
    root: INFO
    com.gym.manage: DEBUG
    org.springframework.r2dbc: DEBUG
    reactor.netty: INFO
  file:
    name: /app/logs/gym-manage.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

五、监控与运维

5.1 监控体系

5.1.1 监控指标

指标类型 具体指标 说明
应用指标 QPS、响应时间、错误率 应用性能监控
JVM 指标 堆内存、GC 次数、线程数 JVM 健康度
数据库指标 连接数、查询时间、慢查询 数据库性能
缓存指标 命中率、内存使用、连接数 缓存效率
消息队列指标 队列长度、消费速率、积压量 消息队列健康度
系统指标 CPU、内存、磁盘、网络 系统资源使用

5.1.2 Prometheus 配置

# monitoring/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'gym-manage'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['gym-manage:8080']
        labels:
          application: 'gym-manage'
          environment: 'prod'
  
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres:5432']
  
  - job_name: 'redis'
    static_configs:
      - targets: ['redis:6379']
  
  - job_name: 'rabbitmq'
    static_configs:
      - targets: ['rabbitmq:15672']

5.1.3 Grafana Dashboard

{
  "dashboard": {
    "title": "健身房管理系统监控",
    "panels": [
      {
        "title": "QPS",
        "targets": [
          {
            "expr": "rate(http_server_requests_seconds_count[1m])"
          }
        ]
      },
      {
        "title": "响应时间 (P99)",
        "targets": [
          {
            "expr": "histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[1m]))"
          }
        ]
      },
      {
        "title": "错误率",
        "targets": [
          {
            "expr": "rate(http_server_requests_seconds_count{status=~\"5..\"}[1m]) / rate(http_server_requests_seconds_count[1m])"
          }
        ]
      },
      {
        "title": "JVM 堆内存",
        "targets": [
          {
            "expr": "jvm_memory_used_bytes{area=\"heap\"}"
          }
        ]
      },
      {
        "title": "GC 次数",
        "targets": [
          {
            "expr": "rate(jvm_gc_pause_seconds_count[1m])"
          }
        ]
      }
    ]
  }
}

5.2 告警规则

5.2.1 告警配置

# monitoring/alerts.yml
groups:
  - name: gym-manage-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~\"5..\"}[5m]) / rate(http_server_requests_seconds_count[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "错误率过高"
          description: "错误率超过 5%,当前值为 {{ $value }}"
      
      - alert: HighResponseTime
        expr: histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "响应时间过长"
          description: "P99 响应时间超过 1s,当前值为 {{ $value }}s"
      
      - alert: HighMemoryUsage
        expr: jvm_memory_used_bytes{area=\"heap\"} / jvm_memory_max_bytes{area=\"heap\"} > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "堆内存使用率过高"
          description: "堆内存使用率超过 80%,当前值为 {{ $value }}"
      
      - alert: DatabaseConnectionPoolExhausted
        expr: hikaricp_connections_active / hikaricp_connections_max > 0.9
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "数据库连接池耗尽"
          description: "数据库连接池使用率超过 90%,当前值为 {{ $value }}"

5.3 日志管理

5.3.1 日志配置

logging:
  level:
    root: INFO
    com.gym.manage: DEBUG
    org.springframework.r2dbc: DEBUG
    reactor.netty: INFO
  file:
    name: /app/logs/gym-manage.log
    max-size: 100MB
    max-history: 30
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

5.3.2 结构化日志

@Slf4j
public class MemberService {
    
    public Mono<Member> getMember(Long id) {
        return memberRepository.findById(id)
            .doOnSubscribe(s -> log.info("开始查询会员: memberId={}", id))
            .doOnNext(m -> log.info("查询到会员: memberId={}, name={}", m.getId(), m.getName()))
            .doOnError(e -> log.error("查询会员失败: memberId={}, error={}", id, e.getMessage()))
            .doOnTerminate(() -> log.info("查询会员完成: memberId={}", id));
    }
}

六、性能优化

6.1 数据库优化

6.1.1 索引优化

-- 会员表索引
CREATE INDEX idx_member_tenant ON member(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_member_store ON member(store_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_member_phone ON member(phone) WHERE deleted_at IS NULL;
CREATE INDEX idx_member_level ON member(level) WHERE deleted_at IS NULL;
CREATE INDEX idx_member_status ON member(status) WHERE deleted_at IS NULL;

-- 预约记录表索引
CREATE INDEX idx_booking_member ON booking_record(member_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_booking_slot ON booking_record(slot_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_booking_coach ON booking_record(coach_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_booking_status ON booking_record(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_booking_time ON booking_record(created_at) WHERE deleted_at IS NULL;

6.1.2 查询优化

// ✅ 正确:使用索引
public Flux<Member> listMembers(Long tenantId, Long storeId) {
    return memberRepository.findByTenantIdAndStoreId(tenantId, storeId);
}

// ❌ 错误:全表扫描
public Flux<Member> listMembers(Long tenantId, Long storeId) {
    return memberRepository.findAll()
        .filter(m -> m.getTenantId().equals(tenantId))
        .filter(m -> m.getStoreId().equals(storeId));
}

6.2 缓存优化

6.2.1 多级缓存

@Service
public class MemberService {
    
    private final MemberRepository memberRepository;
    private final ReactiveRedisTemplate<String, Object> redisTemplate;
    
    public Mono<Member> getMember(Long id) {
        String cacheKey = "member:" + id;
        
        return redisTemplate.opsForValue()
            .get(cacheKey)
            .cast(Member.class)
            .switchIfEmpty(
                memberRepository.findById(id)
                    .flatMap(member -> redisTemplate.opsForValue()
                        .set(cacheKey, member, Duration.ofMinutes(30))
                        .thenReturn(member))
            );
    }
}

6.2.2 缓存策略

数据类型 缓存策略 过期时间
会员信息 Cache-Aside 30 分钟
会员卡信息 Cache-Aside 1 小时
课程列表 Cache-Aside 10 分钟
预约时段 Cache-Aside 5 分钟
配置信息 Write-Through 1 小时

6.3 连接池优化

6.3.1 R2DBC 连接池配置

spring:
  r2dbc:
    pool:
      initial-size: 5          # 初始连接数
      max-size: 20            # 最大连接数
      max-idle-time: 30m      # 最大空闲时间
      max-life-time: 1h       # 最大生命周期
      acquire-timeout: 5s      # 获取连接超时时间

6.3.2 Redis 连接池配置

spring:
  data:
    redis:
      lettuce:
        pool:
          max-active: 20         # 最大连接数
          max-idle: 10          # 最大空闲连接数
          min-idle: 5           # 最小空闲连接数

七、安全设计

7.1 认证授权

7.1.1 JWT 认证

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(
            ServerHttpSecurity http,
            JwtAuthenticationFilter jwtAuthenticationFilter) {
        return http
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/api/v1/auth/**").permitAll()
                .pathMatchers("/actuator/**").permitAll()
                .anyExchange().authenticated())
            .addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
            .csrf(csrf -> csrf.disable())
            .httpBasic(httpBasic -> httpBasic.disable())
            .formLogin(formLogin -> formLogin.disable())
            .build();
    }
}

7.1.2 数据加密

@Component
public class EncryptionService {
    
    private static final String AES_KEY = "your-secret-key";
    private static final String AES_IV = "your-iv";
    
    public String encrypt(String data) {
        // AES 加密
    }
    
    public String decrypt(String encryptedData) {
        // AES 解密
    }
    
    public String maskPhone(String phone) {
        if (phone.length() == 11) {
            return phone.substring(0, 3) + "****" + phone.substring(7);
        }
        return phone;
    }
}

7.2 数据脱敏

@Entity
public class Member {
    
    @Column(name = "phone")
    private String phone;           // 加密存储
    
    @Column(name = "phone_mask")
    private String phoneMask;      // 脱敏显示
    
    @Column(name = "id_card")
    private String idCard;         // 加密存储
    
    @Column(name = "emergency_phone")
    private String emergencyPhone;  // 加密存储
}

八、测试策略

8.1 单元测试

@SpringBootTest
class MemberServiceTest {
    
    @Autowired
    private MemberService memberService;
    
    @MockBean
    private MemberRepository memberRepository;
    
    @Test
    void testGetMember() {
        Member member = Member.builder()
            .id(1L)
            .name("张三")
            .phone("13800138000")
            .build();
        
        when(memberRepository.findById(1L))
            .thenReturn(Mono.just(member));
        
        StepVerifier.create(memberService.getMember(1L))
            .expectNextMatches(m -> m.getName().equals("张三"))
            .verifyComplete();
    }
}

8.2 集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class MemberControllerTest {
    
    @Autowired
    private WebTestClient webTestClient;
    
    @Test
    void testGetMember() {
        webTestClient.get()
            .uri("/api/v1/members/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(Member.class)
            .value(member -> {
                assertThat(member.getName()).isEqualTo("张三");
            });
    }
}

8.3 性能测试

@Test
void testGetMemberPerformance() {
    StepVerifier.withVirtualTime(() -> memberService.getMember(1L))
        .expectNextCount(1)
        .expectComplete()
        .verify(Duration.ofMillis(100));
}

九、总结

9.1 架构优势

高性能

  • 响应式编程,并发能力提升 10 倍
  • 响应时间降低 50%
  • 资源利用率提升 75%

高可用

  • Docker Compose 一键部署
  • 健康检查 + 自动重启
  • 负载均衡 + 故障转移

易维护

  • 单体应用,开发效率高
  • 模块化设计,易于扩展
  • 完善的监控体系

低成本

  • 开发成本降低 37.5%
  • 运维成本降低 40%
  • 服务器资源需求低

9.2 关键成功因素

  1. 响应式编程规范
  2. 模块化设计
  3. 完善的监控体系
  4. 自动化部署
  5. 性能优化

9.3 未来演进

阶段一:单体应用(当前)

  • 模块化设计
  • Docker Compose 部署
  • 性能优化

阶段二:垂直扩展(6-12 个月)

  • 增加服务器资源
  • 优化数据库性能
  • 引入缓存策略

阶段三:水平扩展(12-24 个月)

  • 多实例部署
  • 负载均衡
  • 数据库读写分离

阶段四:微服务(24-36 个月)

  • 按模块拆分服务
  • 服务注册发现
  • 分布式事务