From 104fa7e7c81c7216a38b9226a0640e53563af750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Thu, 5 Mar 2026 13:48:13 +0800 Subject: [PATCH] docs: reorganize documentation structure --- .gitignore | 41 + QUICKSTART.md | 101 + README.md | 124 + .../v1.0}/HLD-系统概要设计.md | 198 ++ .../v1.0}/PRD-产品设计文档.md | 155 ++ docs/customer/产品介绍手册.md | 773 ++++++ docs/design/EVAL-技术架构评估总结.md | 609 +++++ docs/design/HLD-付费订阅版系统概要设计.md | 543 +++++ docs/design/HLD-基础版系统概要设计.md | 474 ++++ docs/design/HLD-技术架构设计.md | 1311 ++++++++++ docs/design/LLD-付费订阅版系统详细设计.md | 1344 +++++++++++ docs/design/LLD-基础版系统详细设计.md | 1346 +++++++++++ docs/design/LLD-系统详细设计.md | 796 ------- docs/design/OPS-部署运维文档.md | 861 +++++++ docs/design/STD-响应式编程规范.md | 945 ++++++++ .../{ => modules}/LLD-会员模块详细设计.md | 5 +- .../{ => modules}/LLD-签到模块详细设计.md | 5 +- .../{ => modules}/LLD-预约模块详细设计.md | 7 +- docs/design/前端安全规范.md | 958 ++++++++ docs/design/前端工程化建设文档.md | 928 ++++++++ docs/design/前端开发规范.md | 1017 ++++++++ docs/design/前端性能优化指南.md | 1154 +++++++++ docs/design/前端技术架构详细设计.md | 1368 +++++++++++ docs/design/前端测试规范.md | 924 ++++++++ docs/plans/2026-02-28-gym-manage-design.md | 2106 ++++++++++++++++- .../2026-03-05-poc-implementation-plan.md | 582 +++++ ...6-03-05-poc-modules-implementation-plan.md | 1639 +++++++++++++ docs/plans/2026-03-05-poc-progress-report.md | 404 ++++ docs/product/PRD-付费订阅版产品设计文档.md | 975 ++++++++ docs/product/PRD-基础版产品设计文档.md | 386 +++ pom.xml | 183 ++ .../com/gym/manage/GymManageApplication.java | 11 + .../controller/booking/BookingController.java | 56 + .../controller/member/MemberController.java | 86 + .../api/dto/request/BookingCreateRequest.java | 17 + .../dto/request/MemberCardCreateRequest.java | 38 + .../api/dto/request/MemberCreateRequest.java | 38 + .../api/dto/request/MemberUpdateRequest.java | 28 + .../dto/response/BookingRecordResponse.java | 27 + .../api/dto/response/MemberCardResponse.java | 35 + .../api/dto/response/MemberResponse.java | 32 + .../application/service/BookingService.java | 139 ++ .../service/MemberCardService.java | 94 + .../application/service/MemberService.java | 134 ++ .../gym/manage/common/constant/ErrorCode.java | 22 + .../common/exception/BusinessException.java | 23 + .../exception/GlobalExceptionHandler.java | 37 + .../com/gym/manage/common/result/Result.java | 32 + .../manage/domain/entity/BookingRecord.java | 60 + .../gym/manage/domain/entity/BookingSlot.java | 60 + .../com/gym/manage/domain/entity/Member.java | 64 + .../gym/manage/domain/entity/MemberCard.java | 77 + .../repository/BookingRecordRepository.java | 18 + .../repository/BookingSlotRepository.java | 32 + .../repository/MemberCardRepository.java | 17 + .../domain/repository/MemberRepository.java | 25 + src/main/resources/application.yml | 44 + src/main/resources/schema.sql | 255 ++ .../gym/manage/GymManageApplicationTests.java | 12 + 59 files changed, 22859 insertions(+), 916 deletions(-) create mode 100644 .gitignore create mode 100644 QUICKSTART.md create mode 100644 README.md rename docs/{design => archive/v1.0}/HLD-系统概要设计.md (81%) rename docs/{product => archive/v1.0}/PRD-产品设计文档.md (82%) create mode 100644 docs/customer/产品介绍手册.md create mode 100644 docs/design/EVAL-技术架构评估总结.md create mode 100644 docs/design/HLD-付费订阅版系统概要设计.md create mode 100644 docs/design/HLD-基础版系统概要设计.md create mode 100644 docs/design/HLD-技术架构设计.md create mode 100644 docs/design/LLD-付费订阅版系统详细设计.md create mode 100644 docs/design/LLD-基础版系统详细设计.md delete mode 100644 docs/design/LLD-系统详细设计.md create mode 100644 docs/design/OPS-部署运维文档.md create mode 100644 docs/design/STD-响应式编程规范.md rename docs/design/{ => modules}/LLD-会员模块详细设计.md (99%) rename docs/design/{ => modules}/LLD-签到模块详细设计.md (99%) rename docs/design/{ => modules}/LLD-预约模块详细设计.md (99%) create mode 100644 docs/design/前端安全规范.md create mode 100644 docs/design/前端工程化建设文档.md create mode 100644 docs/design/前端开发规范.md create mode 100644 docs/design/前端性能优化指南.md create mode 100644 docs/design/前端技术架构详细设计.md create mode 100644 docs/design/前端测试规范.md create mode 100644 docs/plans/2026-03-05-poc-implementation-plan.md create mode 100644 docs/plans/2026-03-05-poc-modules-implementation-plan.md create mode 100644 docs/plans/2026-03-05-poc-progress-report.md create mode 100644 docs/product/PRD-付费订阅版产品设计文档.md create mode 100644 docs/product/PRD-基础版产品设计文档.md create mode 100644 pom.xml create mode 100644 src/main/java/com/gym/manage/GymManageApplication.java create mode 100644 src/main/java/com/gym/manage/api/controller/booking/BookingController.java create mode 100644 src/main/java/com/gym/manage/api/controller/member/MemberController.java create mode 100644 src/main/java/com/gym/manage/api/dto/request/BookingCreateRequest.java create mode 100644 src/main/java/com/gym/manage/api/dto/request/MemberCardCreateRequest.java create mode 100644 src/main/java/com/gym/manage/api/dto/request/MemberCreateRequest.java create mode 100644 src/main/java/com/gym/manage/api/dto/request/MemberUpdateRequest.java create mode 100644 src/main/java/com/gym/manage/api/dto/response/BookingRecordResponse.java create mode 100644 src/main/java/com/gym/manage/api/dto/response/MemberCardResponse.java create mode 100644 src/main/java/com/gym/manage/api/dto/response/MemberResponse.java create mode 100644 src/main/java/com/gym/manage/application/service/BookingService.java create mode 100644 src/main/java/com/gym/manage/application/service/MemberCardService.java create mode 100644 src/main/java/com/gym/manage/application/service/MemberService.java create mode 100644 src/main/java/com/gym/manage/common/constant/ErrorCode.java create mode 100644 src/main/java/com/gym/manage/common/exception/BusinessException.java create mode 100644 src/main/java/com/gym/manage/common/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/gym/manage/common/result/Result.java create mode 100644 src/main/java/com/gym/manage/domain/entity/BookingRecord.java create mode 100644 src/main/java/com/gym/manage/domain/entity/BookingSlot.java create mode 100644 src/main/java/com/gym/manage/domain/entity/Member.java create mode 100644 src/main/java/com/gym/manage/domain/entity/MemberCard.java create mode 100644 src/main/java/com/gym/manage/domain/repository/BookingRecordRepository.java create mode 100644 src/main/java/com/gym/manage/domain/repository/BookingSlotRepository.java create mode 100644 src/main/java/com/gym/manage/domain/repository/MemberCardRepository.java create mode 100644 src/main/java/com/gym/manage/domain/repository/MemberRepository.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/com/gym/manage/GymManageApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eb116d --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Maven +target/ +!.mvn/wrapper/maven-wrapper.jar +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# IDE +.idea/ +*.iws +*.iml +*.ipr +.vscode/ +.settings/ +.classpath +.project + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log + +# Temporary files +*.tmp +*.bak +*.swp +*~ + +# Java +*.class +*.jar +*.war +*.ear diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..3262074 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,101 @@ +# 快速启动指南 + +## 环境要求 + +- JDK 17+ +- Maven 3.9+ +- PostgreSQL 16+ + +## 数据库准备 + +```bash +# 1. 创建数据库 +psql -U postgres +CREATE DATABASE gym_manage; +\q + +# 2. 执行初始化脚本 +psql -U postgres -d gym_manage -f src/main/resources/schema.sql +``` + +## 启动应用 + +```bash +# 1. 编译项目 +mvn clean install + +# 2. 启动应用 +mvn spring-boot:run + +# 或者直接运行jar +java -jar target/gym-manage-1.0.0-SNAPSHOT.jar +``` + +## 访问应用 + +- 应用地址: http://localhost:8080 +- Swagger文档: http://localhost:8080/swagger-ui.html +- 健康检查: http://localhost:8080/actuator/health + +## API测试 + +### 创建会员 + +```bash +curl -X POST http://localhost:8080/api/v1/members \ + -H "Content-Type: application/json" \ + -d '{ + "tenantId": 1, + "storeId": 1, + "name": "张三", + "phone": "13800138000", + "gender": "MALE", + "level": "NORMAL" + }' +``` + +### 查询会员 + +```bash +curl -X GET http://localhost:8080/api/v1/members/1 +``` + +### 创建预约 + +```bash +curl -X POST http://localhost:8080/api/v1/bookings \ + -H "Content-Type: application/json" \ + -d '{ + "memberId": 1, + "slotId": 1 + }' +``` + +## 运行测试 + +```bash +# 运行所有测试 +mvn test + +# 运行特定测试 +mvn test -Dtest=MemberServiceTest +``` + +## 常见问题 + +### 1. 数据库连接失败 + +检查 application.yml 中的数据库配置是否正确。 + +### 2. 端口被占用 + +修改 application.yml 中的 server.port 配置。 + +### 3. 依赖下载失败 + +检查 Maven 仓库配置,或使用阿里云镜像。 + +## 技术支持 + +- 技术负责人: 张翔 +- 邮箱: zhangxiang@example.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d71532 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# 健身房管理系统 POC + +## 项目简介 + +本项目是健身房管理系统的概念验证(POC),采用响应式架构(Spring WebFlux + R2DBC)实现,旨在验证技术方案的可行性和性能指标。 + +## 技术栈 + +- **框架**: Spring Boot 3.2.3 +- **响应式Web**: Spring WebFlux +- **响应式数据访问**: Spring Data R2DBC +- **数据库**: PostgreSQL 16.x +- **数据库驱动**: R2DBC PostgreSQL 1.0.5.RELEASE +- **对象映射**: MapStruct 1.5.5.Final +- **代码简化**: Lombok 1.18.30 +- **API文档**: SpringDoc OpenAPI 2.3.0 +- **测试**: JUnit 5, Reactor Test, Testcontainers + +## 项目结构 + +``` +gym-manage/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/gym/manage/ +│ │ │ ├── api/ # API层 +│ │ │ ├── application/ # 应用层 +│ │ │ ├── domain/ # 领域层 +│ │ │ ├── infrastructure/ # 基础设施层 +│ │ │ └── common/ # 公共模块 +│ │ └── resources/ +│ │ ├── application.yml +│ │ └── schema.sql +│ └── test/ +│ └── java/ +└── pom.xml +``` + +## 快速开始 + +### 前置条件 + +- JDK 17+ +- Maven 3.9+ +- PostgreSQL 16+ + +### 数据库准备 + +```sql +CREATE DATABASE gym_manage; +``` + +### 运行项目 + +```bash +mvn clean install +mvn spring-boot:run +``` + +### 访问API文档 + +- Swagger UI: http://localhost:8080/swagger-ui.html +- OpenAPI JSON: http://localhost:8080/v3/api-docs + +## 核心模块 + +### 会员模块 +- 会员注册、查询、更新 +- 会员卡管理 + +### 预约模块 +- 团课预约 +- 私教预约 +- 时段管理 + +### 签到模块 +- 扫码签到 +- 签到记录查询 + +### 权益模块 +- 权益管理 +- 权益扣减 + +### 订阅模块 +- 模块订阅 +- 计费管理 + +### 营销模块 +- 营销活动管理 +- 推荐奖励 + +### 数据分析模块 +- 统计报表 +- 数据概览 + +## 性能目标 + +- 并发连接数: ≥ 1000 +- API响应时间(P99): < 500ms +- 吞吐量(QPS): ≥ 3000 +- 内存占用: < 1GB +- CPU利用率: < 60% + +## 测试 + +```bash +mvn test +``` + +## 文档 + +- [POC实施计划](docs/plans/2026-03-05-poc-implementation-plan.md) +- [技术架构设计](docs/design/HLD-技术架构设计.md) +- [响应式编程规范](docs/design/STD-响应式编程规范.md) + +## 许可证 + +MIT License + +## 联系方式 + +- 技术负责人: 张翔 +- 邮箱: zhangxiang@example.com diff --git a/docs/design/HLD-系统概要设计.md b/docs/archive/v1.0/HLD-系统概要设计.md similarity index 81% rename from docs/design/HLD-系统概要设计.md rename to docs/archive/v1.0/HLD-系统概要设计.md index 1042258..e86b547 100644 --- a/docs/design/HLD-系统概要设计.md +++ b/docs/archive/v1.0/HLD-系统概要设计.md @@ -138,6 +138,124 @@ └─────────────────────────────────────────────────────────────────────────┘ ``` +### 2.4 产品版本架构 + +本系统采用**基础版 + 订阅模块**的产品架构,满足不同规模和业态的健身房需求: + +#### 2.4.1 基础版 + +基础版保证业务闭环,适合小型工作室、个人教练等场景: + +**包含模块:** + +- ✅ 会员管理(完整) +- ✅ 会员卡管理(完整) +- ✅ 权益管理(完整) +- ✅ 团课预约(完整) +- ✅ 扫码签到(完整) +- ✅ 基础数据统计(完整) +- ✅ 系统管理(基础) + +**限制:** + +- 会员数量:最多500人 +- 门店数量:单门店 +- 团课容量:每节课最多20人 +- 数据保留:保留30天 +- 导出功能:基础导出 + +#### 2.4.2 订阅模块 + +订阅模块按需订阅,灵活扩展功能: + +**业务扩展类模块:** + +- 🔒 私教管理模块 +- 🔒 场地预约模块 +- 🔒 线上课程模块 + +**体验升级类模块:** + +- 🔒 人脸识别签到 +- 🔒 NFC签到 +- 🔒 智能储物柜 + +**营销增长类模块:** + +- 🔒 营销活动模块 +- 🔒 会员推荐奖励 +- 🔒 会员互动社区 + +**数据智能类模块:** + +- 🔒 高级数据分析 +- 🔒 智能报表 +- 🔒 AI运营建议 + +### 2.5 配置层级架构 + +本系统采用**三层配置架构**,支持租户级和门店级配置,支持配置继承和覆盖: + +#### 2.5.1 配置层级 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 配置层级架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 系统默认配置 │ │ +│ │ - 所有模块默认启用 │ │ +│ │ - 基础功能默认配置 │ │ +│ └──────────────────┬──────────────────────────────────────────┘ │ +│ │ 继承 │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 租户级配置 │ │ +│ │ - 租户A:启用团课、私教、营销 │ │ +│ │ - 租户B:只启用私教、营销 │ │ +│ └──────────────────┬──────────────────────────────────────────┘ │ +│ │ 继承/覆盖 │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 门店级配置 │ │ +│ │ - 门店1:继承租户配置 │ │ +│ │ - 门店2:继承租户配置 + 覆盖签到方式 │ │ +│ │ - 门店3:完全自定义配置 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ 查询优先级:门店配置 → 租户配置 → 默认配置 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +#### 2.5.2 继承模式 + +| 继承模式 | 说明 | 适用场景 | +| ------------- | ------------------------------ | ---------------------------------- | +| **继承** | 完全继承上级配置,不做任何修改 | 门店完全使用租户配置 | +| **继承+覆盖** | 继承上级配置,覆盖部分配置项 | 门店大部分使用租户配置,少量自定义 | +| **自定义** | 完全自定义配置,不继承上级配置 | 门店有特殊需求,完全独立配置 | + +#### 2.5.3 配置示例 + +**场景一:连锁品牌 - 门店完全继承** + +- 租户A配置:启用团课、私教、营销 +- 门店1配置:继承模式=继承 +- 最终生效:与租户配置一致 + +**场景二:连锁品牌 - 门店继承+覆盖** + +- 租户A配置:签到方式=[二维码] +- 门店2配置:继承模式=继承+覆盖,签到方式=[二维码,人脸] +- 最终生效:签到方式=[二维码,人脸],其他配置继承租户 + +**场景三:精品工作室 - 完全自定义** + +- 租户B配置:签到方式=[二维码] +- 门店3配置:继承模式=自定义,签到方式=[人脸] +- 最终生效:签到方式=[人脸],不继承租户配置 + --- ## 三、核心业务流程 @@ -404,6 +522,40 @@ - 财务专员:查看财务数据 - 其他角色:按权限查看对应数据 +### 4.6 订阅管理规则 + +#### 4.6.1 订阅套餐规则 + +- 基础版:包含核心业务模块,保证业务闭环 +- 订阅模块:按需订阅,灵活扩展功能 +- 组合套餐:多个模块组合,享受优惠价格 +- 计费方式:月付、季付、半年付、年付,年付享受最大折扣 +- 试用政策:不同模块类型提供不同天数的试用 + +#### 4.6.2 订阅生命周期规则 + +- 订阅状态:正常、暂停、取消、过期 +- 订阅续费:到期前7天提醒,到期当天自动续费 +- 订阅取消:取消后立即生效,不退还当月费用 +- 订阅暂停:暂停期间不扣费,暂停期间无法使用模块 +- 订阅过期:过期后立即禁用模块,数据保留30天 + +#### 4.6.3 订阅模块规则 + +- 模块启用:订阅成功后立即启用,无需重启 +- 模块禁用:取消订阅后立即禁用,数据保留 +- 模块配置:支持租户级和门店级配置,支持继承和覆盖 +- 模块试用:试用期内可免费使用,试用结束后自动续费或取消 +- 模块升级:支持模块升级,升级后立即生效,按差价计费 + +#### 4.6.4 配置继承规则 + +- 查询优先级:门店配置 → 租户配置 → 默认配置 +- 继承模式:继承、继承+覆盖、自定义 +- 配置版本:每次配置变更自动记录版本,支持回滚 +- 配置缓存:配置数据缓存5分钟,配置变更后立即刷新缓存 +- 配置审计:记录所有配置变更操作,支持审计查询 + --- ## 五、业务场景 @@ -502,6 +654,52 @@ - 支持多维度数据分析 - 数据报表支持导出 +#### 5.1.5 租户订阅场景 + +**场景描述**: +租户A是一家连锁健身房品牌,想启用私教管理和营销活动模块,租户管理员登录管理后台,查看订阅套餐,选择私教管理模块和营销活动模块,选择年付方式,查看优惠信息,确认订阅,支付成功,模块立即启用,租户开始使用新功能。 + +**业务流程**: + +1. 租户管理员登录管理后台 +2. 查看订阅套餐 +3. 选择订阅模块 +4. 选择计费方式 +5. 查看优惠信息 +6. 确认订阅 +7. 支付成功 +8. 模块立即启用 +9. 开始使用新功能 + +**涉及的业务规则**: + +- 订阅成功后模块立即启用,无需重启 +- 年付享受最大折扣 +- 支持多种支付方式 +- 订阅成功后发送通知 + +#### 5.1.6 门店配置继承场景 + +**场景描述**: +租户A配置了团课、私教、营销模块,门店1想完全继承租户配置,门店2想在租户配置基础上覆盖签到方式(增加人脸识别),门店3想完全自定义配置。各门店管理员登录管理后台,选择继承模式,配置门店级参数,保存配置,配置立即生效。 + +**业务流程**: + +1. 门店管理员登录管理后台 +2. 查看租户级配置 +3. 选择继承模式(继承/继承+覆盖/自定义) +4. 配置门店级参数 +5. 保存配置 +6. 配置立即生效 +7. 验证配置生效 + +**涉及的业务规则**: + +- 查询优先级:门店配置 → 租户配置 → 默认配置 +- 支持三种继承模式 +- 配置变更后立即生效 +- 配置变更记录版本,支持回滚 + ### 5.2 特殊业务场景 #### 5.2.1 热门课程抢课场景 diff --git a/docs/product/PRD-产品设计文档.md b/docs/archive/v1.0/PRD-产品设计文档.md similarity index 82% rename from docs/product/PRD-产品设计文档.md rename to docs/archive/v1.0/PRD-产品设计文档.md index 9b3b348..c973954 100644 --- a/docs/product/PRD-产品设计文档.md +++ b/docs/archive/v1.0/PRD-产品设计文档.md @@ -35,6 +35,71 @@ | 数据价值 | 提供数据驱动决策支持 | 数据报表使用率 ≥ 80% | | 系统稳定 | 保证高可用性 | SLA ≥ 99.9% | +### 1.4 产品版本架构 + +本系统采用**基础版 + 订阅模块**的产品架构,满足不同规模和业态的健身房需求: + +#### 1.4.1 基础版 + +基础版保证业务闭环,适合小型工作室、个人教练等场景: + +**包含模块:** +- ✅ 会员管理(完整) +- ✅ 会员卡管理(完整) +- ✅ 权益管理(完整) +- ✅ 团课预约(完整) +- ✅ 扫码签到(完整) +- ✅ 基础数据统计(完整) +- ✅ 系统管理(基础) + +**限制:** +- 会员数量:最多500人 +- 门店数量:单门店 +- 团课容量:每节课最多20人 +- 数据保留:保留30天 +- 导出功能:基础导出 + +#### 1.4.2 订阅模块 + +订阅模块按需订阅,灵活扩展功能: + +**业务扩展类模块:** +- 🔒 私教管理模块(¥299/月) +- 🔒 场地预约模块(¥199/月) +- 🔒 线上课程模块(¥399/月) + +**体验升级类模块:** +- 🔒 人脸识别签到(¥499/月) +- 🔒 NFC签到(¥199/月) +- 🔒 智能储物柜(¥299/月) + +**营销增长类模块:** +- 🔒 营销活动模块(¥399/月) +- 🔒 会员推荐奖励(¥299/月) +- 🔒 会员互动社区(¥499/月) + +**数据智能类模块:** +- 🔒 高级数据分析(¥599/月) +- 🔒 智能报表(¥399/月) +- 🔒 AI运营建议(¥799/月) + +**计费方式:** +- 月付:按月计费,每月自动续费 +- 季付:一次性支付3个月,享受9折 +- 半年付:一次性支付6个月,享受85折 +- 年付:一次性支付12个月,享受8折 + 赠送1个月 + +**组合套餐:** +- 基础套餐:基础版 + 私教管理 + 营销活动(¥599/月) +- 高级套餐:基础版 + 全部业务扩展模块(¥799/月) +- 尊享套餐:基础版 + 全部模块(¥1999/月) + +**试用政策:** +- 业务扩展类模块:14天试用 +- 体验升级类模块:7天试用 +- 营销增长类模块:14天试用 +- 数据智能类模块:7天试用 + ### 1.3 目标用户 | 用户角色 | 用户画像 | 核心需求 | @@ -653,6 +718,96 @@ - 支持参数变更记录 - 支持参数导出 +### 2.10 订阅管理 + +#### 2.10.1 订阅套餐管理 + +**用户故事**: 作为超级管理员,我可以管理订阅套餐,以便为租户提供灵活的订阅选择 + +**功能描述**: +- 订阅套餐增删改查 +- 套餐类型管理(基础版、订阅模块、组合套餐) +- 套餐价格配置(月付、季付、半年付、年付) +- 套餐折扣配置 +- 套餐试用天数配置 +- 套餐状态管理(上架、下架) + +**验收标准**: +- 支持套餐分类展示 +- 支持套餐价格自动计算 +- 支持套餐优惠展示 +- 支持套餐试用配置 + +#### 2.10.2 租户订阅管理 + +**用户故事**: 作为超级管理员,我可以管理租户订阅,以便跟踪租户的订阅状态 + +**功能描述**: +- 租户订阅查询 +- 订阅详情查看 +- 订阅状态管理(正常、暂停、取消、过期) +- 订阅续费处理 +- 订阅取消处理 +- 订阅升级/降级处理 + +**验收标准**: +- 支持订阅状态实时更新 +- 支持订阅自动续费 +- 支持订阅变更记录 +- 支持订阅提醒通知 + +#### 2.10.3 订阅模块管理 + +**用户故事**: 作为租户管理员,我可以管理订阅模块,以便按需启用/禁用功能 + +**功能描述**: +- 订阅模块查询 +- 模块启用/禁用 +- 模块配置管理 +- 模块试用期管理 +- 模块使用统计 + +**验收标准**: +- 支持模块即时启用/禁用 +- 支持模块配置继承(门店继承租户配置) +- 支持模块试用期自动结束 +- 支持模块使用量统计 + +#### 2.10.4 订阅计费管理 + +**用户故事**: 作为财务专员,我可以管理订阅计费,以便准确收取订阅费用 + +**功能描述**: +- 订阅账单生成 +- 订阅账单查询 +- 订阅支付处理 +- 订阅退款处理 +- 订阅对账管理 +- 订阅发票管理 + +**验收标准**: +- 支持账单自动生成 +- 支持多种支付方式 +- 支持账单PDF导出 +- 支持对账差异分析 + +#### 2.10.5 订阅配置管理 + +**用户故事**: 作为租户管理员,我可以管理订阅配置,以便灵活调整订阅策略 + +**功能描述**: +- 租户级模块配置 +- 门店级模块配置 +- 配置继承模式管理(继承、继承+覆盖、自定义) +- 配置变更历史查询 +- 配置回滚功能 + +**验收标准**: +- 支持配置层级管理(租户→门店) +- 支持配置继承模式切换 +- 支持配置变更追溯 +- 支持配置版本回滚 + --- ## 三、非功能需求 diff --git a/docs/customer/产品介绍手册.md b/docs/customer/产品介绍手册.md new file mode 100644 index 0000000..ec1c9fb --- /dev/null +++ b/docs/customer/产品介绍手册.md @@ -0,0 +1,773 @@ +# 健身房管理系统产品介绍手册 + +> 版本: v1.0 +> 日期: 2026-03-04 +> 适用对象: 健身房管理者、投资人、合作伙伴 + +--- + +## 一、产品概述 + +### 1.1 产品背景 + +随着健身行业数字化转型的加速,传统健身房面临着会员管理效率低、预约流程繁琐、数据统计困难等痛点。我们的健身房管理系统旨在为健身行业提供一站式数字化解决方案,支持综合型健身俱乐部、精品工作室、连锁品牌等多种业态,实现: + +- **会员端**:一站式查看个人所有信息(会员卡、权益、预约、签到、训练数据) +- **管理后台**:全维度数据整理与分析,支撑运营决策 +- **便捷体验**:约课、签到流程简单高效 +- **灵活配置**:支持业务流程模块化配置,满足不同规模客户需求 +- **订阅模式**:基础版保证业务闭环,订阅模块提供增值服务 + +### 1.2 产品定位 + +我们的产品采用**基础版 + 订阅模块**的创新模式,满足不同规模和业态的健身房需求: + +- **基础版**:保证业务闭环,适合小型工作室、个人教练等场景 +- **订阅模块**:按需订阅,灵活组合,满足中大型健身房、连锁品牌等复杂场景需求 + +### 1.3 核心价值 + +| 价值维度 | 价值描述 | 客户收益 | +| ------------ | ------------------------------ | ----------------- | +| **提升效率** | 自动化会员管理、预约、签到流程 | 人工成本降低50% | +| **优化体验** | 会员端一站式服务,便捷预约签到 | 会员满意度提升30% | +| **数据驱动** | 全维度数据分析,支撑运营决策 | 运营效率提升40% | +| **灵活扩展** | 模块化订阅,按需付费 | IT投入成本降低60% | +| **快速部署** | 云端部署,即开即用 | 上线周期缩短70% | + +--- + +## 二、适用场景 + +我们的产品适用于多种健身业态,满足不同规模客户的需求: + +| 场景类型 | 说明 | 推荐版本 | 典型客户 | +| -------------------- | ---------------------------------------------- | ----------------------- | ------------------------ | +| **精品工作室** | 专注某一类课程,会员规模 100-300 人 | 基础版 | 瑜伽工作室、普拉提工作室 | +| **综合型健身俱乐部** | 多种团课 + 私教 + 器械区,会员规模 500-2000 人 | 基础版 + 体验升级类订阅 | 社区健身房、中型俱乐部 | +| **连锁品牌** | 多门店运营,跨店约课,统一数据管理 | 基础版 + 业务扩展类订阅 | 区域连锁品牌 | +| **大型连锁** | 10+门店,需要精细化运营和数据分析 | 基础版 + 全部订阅模块 | 全国连锁品牌 | + +--- + +## 三、产品版本 + +### 3.1 基础版 + +基础版保证业务闭环,适合小型工作室、个人教练等场景,提供完整的会员管理、预约、签到等核心功能。 + +#### 定价信息 + +| 订阅周期 | 价格 | 折扣 | 说明 | +| ---------- | ----------- | -------- | ------------------ | +| **月付** | ¥299/月 | 标准价格 | 灵活选择,随时调整 | +| **季付** | ¥807/季 | 9折优惠 | 适合短期试用 | +| **半年付** | ¥1,524/半年 | 85折优惠 | 平衡成本与灵活性 | +| **年付** | ¥2,863/年 | 8折优惠 | 最大优惠,长期合作 | + +**试用政策**: + +- 提供14天免费试用 +- 试用期内可随时取消 +- 试用到期后自动续费 + +#### 包含模块 + +| 模块名称 | 功能描述 | 核心价值 | +| ---------------- | -------------------------------------- | ------------------ | +| **会员管理** | 会员注册、信息管理、会员卡管理 | 建立完整的会员档案 | +| **会员卡管理** | 时长卡、次卡、储值卡管理 | 灵活的会员卡体系 | +| **权益管理** | 时长权益、次数权益、储值权益、等级权益 | 多样化的权益体系 | +| **团课预约** | 团课列表、预约、取消、提醒 | 提升课程预约效率 | +| **扫码签到** | 会员扫码签到、签到记录管理 | 快速签到体验 | +| **基础数据统计** | 会员统计、预约统计、签到统计 | 数据可视化展示 | +| **系统管理** | 用户管理、角色权限管理 | 安全的权限控制 | + +#### 技术特点 + +- **云端部署**:无需本地服务器,即开即用 +- **多端支持**:会员小程序、教练端App、管理后台PC +- **高可用性**:系统可用性 ≥ 99.9% +- **数据安全**:数据加密存储,定期备份 + +#### 功能限制 + +- 单门店运营 +- 不支持营销精算模型 +- 不支持自定义促销活动预测 +- 不支持高级数据分析 + +### 3.2 付费订阅版 + +付费订阅版在基础版基础上,提供丰富的增值功能,按需订阅,灵活定价,满足中大型健身房、连锁品牌等复杂场景需求。 + +--- + +## 四、订阅模块体系 + +订阅模块分为四大类别,客户可根据需求灵活订阅: + +### 4.1 业务扩展类 + +适合需要扩展业务范围的健身房。 + +| 模块名称 | 功能描述 | 月费 | 适用场景 | 核心价值 | +| -------------- | -------------------------------------- | ---- | ------------------ | ------------------ | +| **多门店管理** | 支持多门店运营、跨店约课、统一数据管理 | ¥299 | 连锁品牌 | 统一管理,数据互通 | +| **私教管理** | 私教课程管理、教练排班、学员跟进 | ¥199 | 有私教业务的健身房 | 提升私教管理效率 | +| **器械预约** | 器械时段预约、器械使用统计 | ¥99 | 器械资源紧张的场景 | 优化器械使用率 | + +### 4.2 体验升级类 + +适合希望提升会员体验的健身房。 + +| 模块名称 | 功能描述 | 月费 | 适用场景 | 核心价值 | +| ------------- | ------------------------------ | ---- | ------------ | ------------ | +| **人脸识别** | 刷脸签到、无感通行、人脸考勤 | ¥199 | 高端健身房 | 提升签到体验 | +| **NFC一卡通** | NFC手环/卡片签到、储物柜联动 | ¥149 | 传统健身房 | 便捷签到体验 | +| **在线课程** | 线上课程预约、视频点播、直播课 | ¥249 | 混合运营模式 | 拓展线上业务 | + +### 4.3 营销增长类 + +适合需要提升会员增长和留存的健身房。 + +| 模块名称 | 功能描述 | 月费 | 适用场景 | 核心价值 | +| ------------ | ------------------------------ | ---- | -------------- | -------------- | +| **会员营销** | 会员标签、精准营销、自动化营销 | ¥299 | 需要精细化运营 | 提升营销效率 | +| **促销活动** | 优惠券、拼团、秒杀、限时折扣 | ¥199 | 需要促销活动 | 提升会员活跃度 | +| **推荐奖励** | 邀请奖励、裂变营销、会员推荐 | ¥149 | 需要拉新裂变 | 降低获客成本 | + +### 4.4 数据智能类 + +适合需要数据驱动决策的健身房。 + +| 模块名称 | 功能描述 | 月费 | 适用场景 | 核心价值 | +| ------------------ | -------------------------------- | ---- | ---------------- | ---------------- | +| **营销精算模型** | 基于历史数据的促销策略预测 | ¥499 | 需要数据驱动决策 | 优化营销ROI | +| **自定义促销预测** | 多维度自定义促销活动效果预测 | ¥399 | 需要灵活促销策略 | 精准预测活动效果 | +| **高级数据分析** | 会员行为分析、流失预警、收入预测 | ¥399 | 需要深度数据分析 | 数据驱动运营决策 | + +--- + +## 五、计费方式 + +我们提供灵活的计费方式,满足不同客户的预算需求。 + +### 5.1 付费模式选择 + +我们提供两种付费模式,客户可根据自身情况选择: + +#### 模式A:固定月费模式 + +**适合客户**:交易量小、预算稳定的客户 + +**计费方式**: + +- 基础版:¥299/月 +- 订阅模块:按模块定价(¥99-499/月) +- 订阅周期:月付/季付/半年付/年付(享受相应折扣) + +**优势**: + +- 成本可预测,便于预算管理 +- 无交易量限制 +- 适合业务稳定的客户 + +#### 模式B:成功费模式 + +**适合客户**:交易量大、希望按量付费的客户 + +**计费方式**: + +- 基础版:交易额的1%-1.5% +- 订阅模块:交易额的0.3%-0.8% +- 交易额包括:会员卡充值、会员卡消费、私教课程购买、促销活动交易等 + +**优势**: + +- 完全按使用量付费,降低门槛 +- 系统收益与客户业务增长绑定 +- 适合交易量大的客户 + +**切换机制**: + +- 客户可随时在两种模式间切换 +- 切换后下个计费周期生效 +- 提供计算器帮助客户对比两种模式成本 + +### 5.2 订阅周期优惠 + +| 订阅周期 | 折扣力度 | 说明 | +| ---------- | -------- | ------------------ | +| **月付** | 标准价格 | 灵活选择,随时调整 | +| **季付** | 9折优惠 | 适合短期试用 | +| **半年付** | 85折优惠 | 平衡成本与灵活性 | +| **年付** | 8折优惠 | 最大优惠,长期合作 | + +### 5.3 行业类型推荐套餐 + +我们根据不同行业类型的特点,预设推荐套餐,同时采用动态折扣(模块越多,折扣越大)。 + +#### 行业类型 + +**1. 瑜伽工作室** + +- 特点:会员规模小(100-300人)、课程单一、预算有限 +- 核心需求:会员管理、团课预约、基础统计 +- 推荐模块:在线课程、会员营销 + +**2. 综合健身房** + +- 特点:会员规模中等(500-2000人)、业务多样、需要私教 +- 核心需求:会员管理、团课预约、私教管理、基础统计 +- 推荐模块:私教管理、器械预约、人脸识别、会员营销 + +**3. 连锁品牌** + +- 特点:会员规模大(2000+人)、多门店、需要精细化运营 +- 核心需求:全功能 + 多门店管理 + 数据分析 +- 推荐模块:多门店管理、全部营销模块、全部数据智能模块 + +#### 动态折扣规则 + +| 订阅模块数量 | 折扣力度 | +| ------------ | -------- | +| 1个模块 | 9.5折 | +| 2个模块 | 9折 | +| 3个模块 | 8.5折 | +| 4-5个模块 | 8折 | +| 6-8个模块 | 7.5折 | +| 9-11个模块 | 7折 | +| 全部12个模块 | 6.5折 | + +#### 推荐套餐 + +**🧘 瑜伽工作室推荐套餐** + +_入门套餐_(适合小型工作室) + +- 包含:基础版 + 在线课程 +- 模块数量:1个 +- 折扣:9.5折 +- 月费:¥299 + ¥249 × 0.95 = **¥536** + +_成长套餐_(适合中型工作室) + +- 包含:基础版 + 在线课程 + 会员营销 +- 模块数量:2个 +- 折扣:9折 +- 月费:¥299 + (¥249 + ¥299) × 0.9 = **¥763** + +**🏋️ 综合健身房推荐套餐** + +_标准套餐_(适合小型健身房) + +- 包含:基础版 + 私教管理 + 器械预约 +- 模块数量:2个 +- 折扣:9折 +- 月费:¥299 + (¥199 + ¥99) × 0.9 = **¥538** + +_专业套餐_(适合中型健身房) + +- 包含:基础版 + 私教管理 + 器械预约 + 人脸识别 + 会员营销 +- 模块数量:4个 +- 折扣:8折 +- 月费:¥299 + (¥199 + ¥99 + ¥199 + ¥299) × 0.8 = **¥875** + +**🏢 连锁品牌推荐套餐** + +_企业套餐_(适合区域连锁) + +- 包含:基础版 + 多门店管理 + 全部营销模块(3个) +- 模块数量:4个 +- 折扣:8折 +- 月费:¥299 + (¥299 + ¥299 + ¥199 + ¥149) × 0.8 = **¥1,116** + +_旗舰套餐_(适合全国连锁) + +- 包含:基础版 + 全部订阅模块(12个) +- 模块数量:12个 +- 折扣:6.5折 +- 月费:¥299 + ¥3,590 × 0.65 = **¥2,633** + +### 5.4 客户选择流程 + +1. **选择行业类型**:瑜伽工作室 / 综合健身房 / 连锁品牌 +2. **查看推荐套餐**:系统根据行业类型推荐2-3个套餐 +3. **自定义或选择**:客户可以选择推荐套餐,或自定义模块组合 +4. **选择计费模式**:固定月费 / 成功费模式 +5. **系统自动计算**:根据模块数量和计费模式计算月费 + +### 5.5 智能动态推荐 + +我们提供智能动态推荐系统,根据您的业务发展自动调整推荐套餐。 + +#### 5.5.1 初始推荐 + +**推荐维度**: + +- 行业类型(瑜伽工作室 / 综合健身房 / 连锁品牌) +- 员工数量(教练、前台、管理人员总数) +- 会员数量(当前会员总数) +- 门店数量(门店总数) +- 月交易额(月度交易总额) + +**推荐算法**: + +- 收集客户规模信息 +- 计算规模得分(0-100分) +- 匹配推荐套餐 +- 提供上下两个套餐供选择 + +#### 5.5.2 动态调整 + +**触发时机**: + +- 会员数量增长超过阈值(如增长50%) +- 月交易额增长超过阈值(如增长30%) +- 门店数量增加(如新增门店) +- 员工数量增加(如新增员工) +- 季度业务回顾(每季度自动评估) + +**调整策略**: + +- 升级推荐:业务增长后,推荐更高级的套餐 +- 降级推荐:业务萎缩后,推荐更经济的套餐 +- 模块调整:根据业务变化,推荐增减订阅模块 +- 个性化推荐:基于历史行为和行业趋势调整推荐 + +#### 5.5.3 推荐通知 + +**通知方式**: + +- 系统通知:在管理后台显示推荐提示 +- 邮件通知:发送推荐建议到客户邮箱 +- 短信通知:重要推荐变更发送短信提醒 +- 客服跟进:客服主动联系客户,解释推荐理由 + +**通知内容**: + +- 当前套餐分析:当前套餐的使用情况 +- 业务变化分析:业务指标的变化情况 +- 推荐理由:为什么推荐新套餐 +- 对比分析:新旧套餐的对比 +- 预期收益:切换到新套餐的预期收益 + +#### 5.5.4 推荐示例 + +**场景1:会员数量增长** + +**初始状态**: + +- 行业类型:综合健身房 +- 员工数量:8人 +- 会员数量:300人 +- 当前套餐:标准套餐(¥538/月) + +**业务变化**: + +- 会员数量增长到600人(增长100%) + +**动态推荐**: + +- 推荐套餐:专业套餐(¥875/月) +- 推荐理由:会员数量增长,需要更多营销和数据分析功能 +- 预期收益:提升会员留存率,增加营销效率 + +--- + +**场景2:门店数量增加** + +**初始状态**: + +- 行业类型:连锁品牌 +- 门店数量:2家 +- 会员数量:800人 +- 当前套餐:企业套餐(¥1,116/月) + +**业务变化**: + +- 门店数量增加到5家(增长150%) + +**动态推荐**: + +- 推荐套餐:专业套餐(¥2,067/月) +- 推荐理由:门店数量增加,需要更多数据智能功能 +- 预期收益:提升跨店运营效率,增强数据分析能力 + +--- + +**场景3:月交易额增长** + +**初始状态**: + +- 行业类型:瑜伽工作室 +- 员工数量:3人 +- 会员数量:80人 +- 月交易额:¥20,000 +- 当前套餐:入门套餐(¥536/月) + +**业务变化**: + +- 月交易额增长到¥50,000(增长150%) + +**动态推荐**: + +- 推荐套餐:成长套餐(¥763/月) +- 推荐理由:交易额增长,需要更多营销功能 +- 预期收益:提升营销效率,增加会员活跃度 + +--- + +### 5.6 试用政策 + +- **免费试用**:所有订阅模块提供14天免费试用 +- **随时取消**:试用期内可随时取消,无需任何费用 +- **自动续费**:试用到期后自动续费,可提前取消 + +--- + +## 六、客户收益分析 + +### 6.1 成本节约 + +| 成本类型 | 传统方式 | 使用我们的系统 | 节约比例 | +| ------------ | ---------------------------- | -------------------- | -------- | +| **人工成本** | 需要专人管理会员、预约、签到 | 自动化处理,减少人工 | 50% | +| **IT投入** | 自建系统,服务器、维护成本高 | 云端部署,按需付费 | 60% | +| **营销成本** | 传统营销,效果难以评估 | 精准营销,数据驱动 | 40% | +| **运营成本** | 手工统计,效率低下 | 自动统计,实时分析 | 30% | + +### 6.2 效率提升 + +| 业务场景 | 传统方式 | 使用我们的系统 | 效率提升 | +| ------------ | -------------------- | ------------------ | -------- | +| **会员注册** | 纸质登记,信息录入慢 | 在线注册,自动建档 | 70% | +| **课程预约** | 电话预约,人工确认 | 在线预约,自动确认 | 80% | +| **签到管理** | 人工核对,耗时耗力 | 扫码签到,秒级完成 | 90% | +| **数据统计** | 手工统计,周期长 | 自动统计,实时查看 | 95% | + +### 6.3 收入增长 + +| 增长维度 | 增长方式 | 预估增长 | +| ------------ | -------------------- | -------- | +| **会员留存** | 精准营销、个性化服务 | 提升20% | +| **会员增长** | 裂变营销、推荐奖励 | 提升30% | +| **课程收入** | 优化排课、提升预约率 | 提升15% | +| **私教收入** | 私教管理、学员跟进 | 提升25% | + +--- + +## 七、成功案例(示例) + +### 7.1 小型工作室案例 + +**客户背景**:某瑜伽工作室,3名教练,200名会员 + +**使用方案**:基础版 + +**实施效果**: + +- 会员预约效率提升80% +- 教练排课时间节省60% +- 会员满意度提升25% +- 月度运营成本降低40% + +### 7.2 连锁品牌案例 + +**客户背景**:某区域连锁健身品牌,5家门店,3000名会员 + +**使用方案**:基础版 + 多门店管理 + 会员营销 + 高级数据分析 + +**实施效果**: + +- 统一管理,数据互通 +- 会员留存率提升22% +- 营销ROI提升35% +- 跨店预约率提升40% + +### 7.3 大型俱乐部案例 + +**客户背景**:某综合型健身俱乐部,20名教练,1500名会员 + +**使用方案**:基础版 + 私教管理 + 人脸识别 + 营销精算模型 + +**实施效果**: + +- 私教收入增长28% +- 签到体验大幅提升 +- 营销活动ROI提升40% +- 会员活跃度提升30% + +--- + +## 八、服务保障 + +### 8.1 技术保障 + +- **系统可用性**:≥ 99.9% +- **数据备份**:每日自动备份 +- **安全防护**:数据加密、访问控制 +- **技术支持**:7×24小时在线支持 + +### 8.2 服务承诺 + +- **快速部署**:签约后3个工作日内完成部署 +- **培训支持**:提供系统使用培训 +- **持续优化**:定期系统升级优化 +- **专属客服**:一对一客户服务 + +### 8.3 退款政策 + +- **试用期内**:随时退款,全额退还 +- **正式使用**:按比例退还剩余费用 +- **无理由退款**:7天内无理由退款 + +--- + +## 九、联系我们 + +### 9.1 商务咨询 + +- **电话**:400-XXX-XXXX +- **邮箱**:sales@example.com +- **微信**:扫描二维码添加商务顾问 + +### 9.2 技术支持 + +- **电话**:400-XXX-XXXX +- **邮箱**:support@example.com +- **工单系统**:在线提交工单 + +### 9.3 公司地址 + +- **总部**:北京市朝阳区XXX大厦 +- **研发中心**:上海市浦东新区XXX园区 + +--- + +## 附录:常见问题 + +### Q1: 基础版是否需要额外付费? + +**A**: 基础版采用订阅制,月费¥XXX,包含所有基础功能,无需额外付费。 + +### Q2: 订阅模块是否可以随时取消? + +**A**: 可以。订阅模块支持随时取消,取消后该模块功能将无法使用,但已产生的费用不予退还。 + +### Q3: 是否支持数据导出? + +**A**: 支持。所有数据均可导出为Excel或CSV格式,方便客户进行二次分析。 + +### Q4: 是否支持自定义品牌? + +**A**: 支持。付费订阅版支持自定义品牌Logo、颜色、域名等,打造专属品牌形象。 + +### Q5: 是否支持多语言? + +**A**: 目前支持中文和英文,后续将支持更多语言。 + +### Q6: 数据安全如何保障? + +**A**: 我们采用银行级数据加密技术,数据存储在阿里云/腾讯云,定期备份,确保数据安全。 + +### Q7: 是否提供API接口? + +**A**: 付费订阅版提供完整的API接口,支持与第三方系统对接。 + +### Q8: 是否支持私有化部署? + +**A**: 企业套餐支持私有化部署,满足数据安全要求高的客户需求。 + +--- + +## 十、未来优化计划 + +我们持续优化产品和服务,为您提供更好的体验。以下是我们的优化计划: + +### 10.1 短期优化(1-3个月) + +#### 1. 首月特惠 + +**方案描述**:新客户首月5折优惠 + +**适用对象**:首次注册的新客户 + +**优惠力度**: + +- 基础版:¥149.5/月(原价¥299) +- 订阅模块:按原价5折计算 + +**限制条件**: + +- 首月必须选择固定月费模式 +- 同一手机号/身份证号3个月内只能享受一次 + +**预期效果**: + +- 降低获客成本50% +- 转化率提升20-30% +- 快速扩大用户基数 + +--- + +#### 2. 模块独立试用 + +**方案描述**:每个订阅模块独立14天试用 + +**试用规则**: + +- 每个模块独立14天试用 +- 可同时试用多个模块,每个模块独立计时 +- 模块A试用后转正,模块B仍可继续试用 + +**预期效果**: + +- 降低试用门槛 +- 模块订阅率提升15-20% +- 客单价提升10-15% + +--- + +#### 3. 在线计算器 + +**方案描述**:提供在线计费计算器,帮助客户对比两种付费模式 + +**计算功能**: + +- 固定月费模式:根据选择的模块数量和订阅周期计算月费 +- 成功费模式:根据预估月交易额计算月费 +- 模式对比:自动计算两种模式的成本,推荐更优模式 + +**输入参数**: + +- 行业类型(瑜伽工作室/综合健身房/连锁品牌) +- 预估月交易额(成功费模式) +- 选择模块数量 +- 订阅周期(月付/季付/半年付/年付) + +**预期效果**: + +- 决策时间缩短83%(从30分钟缩短到5分钟) +- 转化率提升10-15% +- 客户满意度提升 + +--- + +### 10.2 中期优化(3-6个月) + +#### 1. 忠诚折扣 + +**方案描述**:连续订阅3年以上,额外享受95折优惠 + +**适用条件**: + +- 连续订阅满36个月(3年) +- 在当前折扣基础上额外95折 +- 适用范围:基础版 + 所有订阅模块 + +**重置条件**:中断订阅后,忠诚期重新计算 + +**预期效果**: + +- 留存率提升15-20% +- 客单价提升10-15% +- 收入稳定性提升 + +--- + +#### 2. 推荐奖励 + +**方案描述**:老客户推荐新客户,双方获得优惠 + +**推荐人奖励**: + +- 推荐成功:获得1个月免费订阅或等值优惠券 +- 推荐数量:无上限,鼓励持续推荐 + +**被推荐人奖励**: + +- 新客户注册:首月5折优惠(可与首月特惠叠加) +- 必须输入推荐码才能享受优惠 + +**奖励发放**:推荐成功后7天内发放 + +**预期效果**: + +- 获客成本降低50-70% +- 获客速度提升30-40% +- 客户粘性提升20-30% + +--- + +#### 3. 行业扩展 + +**方案描述**:增加普拉提工作室、拳击馆、游泳馆等行业类型 + +**新增行业类型**: + +**🧘 普拉提工作室** + +- 特点:会员规模小(50-200人)、课程单一、预算有限 +- 核心需求:会员管理、团课预约、基础统计 +- 推荐模块:在线课程、会员营销 +- 推荐套餐: + - 入门套餐:基础版 + 在线课程(¥536/月) + - 成长套餐:基础版 + 在线课程 + 会员营销(¥763/月) + +**🥊 拳击馆** + +- 特点:会员规模小(100-300人)、课程多样、需要私教 +- 核心需求:会员管理、团课预约、私教管理 +- 推荐模块:私教管理、器械预约、会员营销 +- 推荐套餐: + - 标准套餐:基础版 + 私教管理 + 器械预约(¥538/月) + - 专业套餐:基础版 + 私教管理 + 器械预约 + 会员营销(¥875/月) + +**🏊 游泳馆** + +- 特点:会员规模中等(200-500人)、课程单一、时段管理复杂 +- 核心需求:会员管理、团课预约、时段管理 +- 推荐模块:器械预约、会员营销 +- 推荐套餐: + - 标准套餐:基础版 + 器械预约(¥398/月) + - 成长套餐:基础版 + 器械预约 + 会员营销(¥623/月) + +**预期效果**: + +- 市场覆盖扩大50% +- 转化率提升15-20% +- 客单价提升5-10% + +--- + +### 10.3 优化优先级 + +| 优化项 | 实施周期 | 预期效果 | 优先级 | +| ------------------------ | -------- | -------------------------- | ------ | +| 在线计算器 | 1个月 | 决策时间-80%,转化率+12% | 🔴 高 | +| 首月特惠 | 1个月 | 转化率+25%,获客成本-50% | 🔴 高 | +| 模块独立试用 | 2-3个月 | 模块渗透率+18%,客单价+12% | 🟡 中 | +| 行业扩展(普拉提、拳击) | 2-3个月 | 市场覆盖+30%,转化率+17% | 🟡 中 | +| 推荐奖励 | 4-6个月 | 获客成本-60%,转化率+35% | 🟡 中 | +| 行业扩展(游泳馆) | 4-6个月 | 市场覆盖+20%,转化率+15% | 🟡 中 | +| 忠诚折扣 | 7-12个月 | 留存率+18%,客单价+12% | 🟢 低 | + +**综合预期**: + +- 转化率提升:30-40% +- 获客成本降低:50-60% +- 留存率提升:15-20% +- 客单价提升:10-15% + +--- + +**感谢您选择我们的产品!** + +我们致力于为健身行业提供最优质的数字化解决方案,助力您的业务增长。如有任何疑问,请随时联系我们。 + +--- + +_文档版本: v1.0_ +_最后更新: 2026-03-04_ diff --git a/docs/design/EVAL-技术架构评估总结.md b/docs/design/EVAL-技术架构评估总结.md new file mode 100644 index 0000000..ddf3fdd --- /dev/null +++ b/docs/design/EVAL-技术架构评估总结.md @@ -0,0 +1,609 @@ +# 技术架构评估总结报告 + +> 文档编号: GYM-EVAL-TECH-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建技术架构评估总结 | + +--- + +## 参考文档 + +- 《健身房管理系统技术架构设计文档》 GYM-HLD-TECH-001 +- 《健身房管理系统响应式编程规范文档》 GYM-STD-REACTIVE-001 +- 《健身房管理系统部署运维文档》 GYM-OPS-DEPLOY-001 + +--- + +## 一、评估概述 + +### 1.1 评估背景 + +健身房管理系统是一个面向健身房的综合管理平台,支持会员管理、预约管理、签到管理、权益管理、订阅管理、营销管理等核心功能。系统需要支持高并发、低延迟、高可用、易扩展等特性。 + +### 1.2 评估目标 + +1. 评估技术架构的可行性和合理性 +2. 评估技术栈的成熟度和适用性 +3. 评估开发成本和运维成本 +4. 评估风险和缓解策略 +5. 提供技术选型建议 + +### 1.3 评估方法 + +1. 文档分析:分析现有设计文档 +2. 技术调研:调研相关技术栈 +3. 性能评估:评估性能指标和预期 +4. 成本分析:分析开发成本和运维成本 +5. 风险评估:识别风险和制定缓解策略 + +--- + +## 二、技术选型评估 + +### 2.1 架构选型 + +#### 2.1.1 单体应用 vs 微服务 + +| 评估维度 | 单体应用 | 微服务 | 评估结果 | +|---------|---------|--------|---------| +| **开发复杂度** | 低 | 高 | ✅ 单体应用优势明显 | +| **部署复杂度** | 低 | 高 | ✅ 单体应用优势明显 | +| **事务管理** | 简单 | 复杂 | ✅ 单体应用优势明显 | +| **调试难度** | 低 | 高 | ✅ 单体应用优势明显 | +| **性能开销** | 低 | 高 | ✅ 单体应用优势明显 | +| **初期成本** | 低 | 高 | ✅ 单体应用优势明显 | +| **扩展性** | 垂直扩展 | 水平扩展 | ⚠️ 微服务优势明显 | +| **故障隔离** | 差 | 好 | ⚠️ 微服务优势明显 | + +**评估结论**:✅ **推荐单体应用** + +**理由**: +1. 适合当前规模(1000 并发用户) +2. 适合团队规模(3-5 人) +3. 开发效率高,学习成本低 +4. 部署简单,运维成本低 +5. 性能优秀,无服务间调用开销 + +**未来演进**: +- 阶段一:单体应用(当前) +- 阶段二:垂直扩展(6-12 个月) +- 阶段三:水平扩展(12-24 个月) +- 阶段四:微服务(24-36 个月) + +#### 2.1.2 响应式编程 vs 传统编程 + +| 评估维度 | Spring MVC + JPA | WebFlux + R2DBC | 评估结果 | +|---------|-----------------|-----------------|---------| +| **并发能力** | 200-500 | 2000-5000 | ✅ WebFlux + R2DBC 优势明显 | +| **API 响应时间 (P99)** | 500-800ms | 200-400ms | ✅ WebFlux + R2DBC 优势明显 | +| **吞吐量 (QPS)** | 500-1000 | 3000-5000 | ✅ WebFlux + R2DBC 优势明显 | +| **内存占用** | 2-4GB | 512MB-1GB | ✅ WebFlux + R2DBC 优势明显 | +| **CPU 利用率** | 60-80% | 40-60% | ✅ WebFlux + R2DBC 优势明显 | +| **线程数** | 200-500 | 10-20 | ✅ WebFlux + R2DBC 优势明显 | +| **开发效率** | 高 | 中 | ⚠️ Spring MVC + JPA 优势明显 | +| **学习成本** | 低 | 高 | ⚠️ Spring MVC + JPA 优势明显 | +| **调试难度** | 低 | 高 | ⚠️ Spring MVC + JPA 优势明显 | +| **生态成熟度** | 高 | 中 | ⚠️ Spring MVC + JPA 优势明显 | + +**评估结论**:✅ **推荐 WebFlux + R2DBC** + +**理由**: +1. 性能优势明显(并发能力提升 10 倍) +2. 响应时间降低 50% +3. 资源利用率提升 75% +4. 适合高并发场景(预约、签到) +5. 统一技术栈,架构简洁 + +**前提条件**: +1. 团队培训(4-6 周) +2. 建立响应式编程规范 +3. 完善监控和调试体系 +4. 代码审查(100% 覆盖) +5. 专项测试(单元测试 + 集成测试 + 性能测试) + +### 2.2 技术栈评估 + +#### 2.2.1 核心技术栈 + +| 技术组件 | 版本 | 成熟度 | 社区活跃度 | 文档质量 | 推荐度 | +|---------|------|-------|-----------|---------|-------| +| **Spring Boot** | 3.2.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Spring WebFlux** | 3.2.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Spring Data R2DBC** | 3.2.x | ⭐⭐⭐⭐ | 高 | 良好 | ✅ 推荐 | +| **PostgreSQL R2DBC** | 1.0.0.RELEASE | ⭐⭐⭐⭐ | 高 | 良好 | ✅ 推荐 | +| **Spring Security** | 6.2.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Redis Reactive** | 3.2.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **RabbitMQ** | 3.12.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Elasticsearch** | 8.11.x | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Prometheus** | Latest | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | +| **Grafana** | Latest | ⭐⭐⭐⭐⭐ | 高 | 优秀 | ✅ 强烈推荐 | + +**评估结论**:✅ **技术栈成熟,社区活跃,文档完善** + +#### 2.2.2 数据库选型 + +| 数据库 | R2DBC 支持 | 性能 | 可靠性 | 扩展性 | 推荐度 | +|-------|-----------|------|-------|-------|-------| +| **PostgreSQL** | ✅ 完全支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ✅ 强烈推荐 | +| **MySQL** | ✅ 完全支持 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ✅ 推荐 | +| **Oracle** | ⚠️ 支持有限 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ 不推荐 | +| **SQL Server** | ⚠️ 支持有限 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ❌ 不推荐 | + +**评估结论**:✅ **推荐 PostgreSQL** + +**理由**: +1. 完全支持 R2DBC +2. 金融级数据库,支持 ACID 事务 +3. JSONB 支持,适合配置管理 +4. 全文搜索支持 +5. 社区活跃,文档完善 + +#### 2.2.3 缓存选型 + +| 缓存 | Reactive 支持 | 性能 | 功能 | 推荐度 | +|------|-------------|------|------|-------| +| **Redis** | ✅ 完全支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 强烈推荐 | +| **Memcached** | ❌ 不支持 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ❌ 不推荐 | +| **本地缓存(Caffeine)** | ✅ 支持 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ✅ 推荐 | + +**评估结论**:✅ **推荐 Redis + Caffeine** + +**理由**: +1. Redis 完全支持 Reactive +2. 性能优秀 +3. 功能丰富(分布式锁、过期策略) +4. Caffeine 本地缓存,减少网络开销 + +--- + +## 三、性能评估 + +### 3.1 性能基准 + +#### 3.1.1 预期性能指标 + +| 性能指标 | Spring MVC + JPA | WebFlux + R2DBC | 提升幅度 | +|---------|-----------------|-----------------|---------| +| **并发连接数** | 200-500 | 2000-5000 | **10x** | +| **API 响应时间 (P99)** | 500-800ms | 200-400ms | **50%↓** | +| **吞吐量 (QPS)** | 500-1000 | 3000-5000 | **5x** | +| **内存占用** | 2-4GB | 512MB-1GB | **75%↓** | +| **CPU 利用率** | 60-80% | 40-60% | **25%↓** | +| **线程数** | 200-500 | 10-20 | **95%↓** | + +#### 3.1.2 场景化性能预测 + +**场景 1:预约高峰期(每天 18:00-20:00)** + +``` +业务场景:会员预约团课 +并发用户:500-1000 +请求频率:每秒 50-100 次预约请求 + +Spring MVC + JPA: +- 需要服务器:4-6 台(8核16G) +- 响应时间:600-1000ms +- 成功率:95-97% + +WebFlux + R2DBC: +- 需要服务器:1-2 台(4核8G) +- 响应时间:200-400ms +- 成功率:99%+ + +成本节省:60-70% +``` + +**场景 2:签到高峰期(每天 07:00-09:00, 18:00-20:00)** + +``` +业务场景:会员扫码签到 +并发用户:1000-2000 +请求频率:每秒 100-200 次签到请求 + +Spring MVC + JPA: +- 需要服务器:6-8 台(8核16G) +- 响应时间:300-500ms +- 成功率:98-99% + +WebFlux + R2DBC: +- 需要服务器:2-3 台(4核8G) +- 响应时间:100-200ms +- 成功率:99.9%+ + +成本节省:70-80% +``` + +**场景 3:实时数据查询(会员信息、课程列表)** + +``` +业务场景:小程序实时查询 +并发用户:2000-3000 +请求频率:每秒 200-300 次查询请求 + +Spring MVC + JPA: +- 需要服务器:8-10 台(8核16G) +- 响应时间:200-400ms +- 缓存命中率:60-70% + +WebFlux + R2DBC: +- 需要服务器:3-4 台(4核8G) +- 响应时间:50-150ms +- 缓存命中率:80-90% + +成本节省:70-75% +``` + +### 3.2 性能优化策略 + +#### 3.2.1 数据库优化 + +1. **索引优化**:为常用查询字段创建索引 +2. **查询优化**:避免全表扫描,使用索引 +3. **连接池优化**:合理配置连接池大小 +4. **分区表**:对大表进行分区 + +#### 3.2.2 缓存优化 + +1. **多级缓存**:本地缓存 + Redis 缓存 +2. **缓存策略**:Cache-Aside 模式 +3. **缓存预热**:系统启动时预热热点数据 +4. **缓存更新**:合理设置缓存过期时间 + +#### 3.2.3 应用优化 + +1. **JVM 调优**:合理配置堆内存和 GC 参数 +2. **连接池调优**:合理配置数据库连接池和 Redis 连接池 +3. **异步处理**:使用消息队列异步处理耗时操作 +4. **限流熔断**:使用 Sentinel 实现限流和熔断 + +--- + +## 四、成本分析 + +### 4.1 开发成本评估 + +| 成本项 | Spring MVC + JPA | WebFlux + R2DBC | 差异 | +|-------|-----------------|-----------------|------| +| **学习成本** | 低(团队熟悉) | 高(需要培训) | +30-40% | +| **开发效率** | 高(成熟生态) | 中(响应式编程复杂) | -20-30% | +| **代码复杂度** | 低 | 高 | +40-50% | +| **测试成本** | 中 | 高(响应式测试复杂) | +30-40% | +| **调试成本** | 低 | 高(异步调试困难) | +50-60% | +| **文档成本** | 低 | 高(需要详细规范) | +40-50% | + +**总体开发成本增加:40-60%** + +### 4.2 运维成本评估 + +| 成本项 | Spring MVC + JPA | WebFlux + R2DBC | 差异 | +|-------|-----------------|-----------------|------| +| **服务器成本** | 高(需要更多服务器) | 低(资源利用率高) | **-60-70%** | +| **数据库成本** | 高(连接数多) | 低(连接数少) | **-50-60%** | +| **监控成本** | 中 | 高(需要专门工具) | +30-40% | +| **故障排查成本** | 低 | 高(异步问题难定位) | +50-60% | +| **升级维护成本** | 低 | 中(生态更新快) | +20-30% | + +**总体运维成本降低:40-50%** + +### 4.3 总拥有成本(TCO)分析 + +``` +3 年 TCO 对比(假设 1000 并发用户): + +Spring MVC + JPA: +- 开发成本:100 万 +- 服务器成本:50 万/年 × 3 = 150 万 +- 运维成本:20 万/年 × 3 = 60 万 +- 总计:310 万 + +WebFlux + R2DBC: +- 开发成本:160 万(+60%) +- 服务器成本:20 万/年 × 3 = 60 万(-60%) +- 运维成本:30 万/年 × 3 = 90 万(+50%) +- 总计:310 万 + +结论:3 年 TCO 基本持平,但 WebFlux + R2DBC 在长期扩展性上优势明显 +``` + +--- + +## 五、风险评估与缓解 + +### 5.1 技术风险矩阵 + +| 风险项 | 概率 | 影响 | 风险等级 | 缓解策略 | +|-------|------|------|---------|---------| +| **事务一致性** | 高 | 高 | 🔴 严重 | R2DBC 事务 + 分布式锁 + Saga 模式 | +| **团队技能不足** | 中 | 高 | 🔴 严重 | 培训 + 代码审查 + 技术分享 | +| **调试困难** | 高 | 中 | 🟡 中等 | Reactor Debug + 专项测试 | +| **生态成熟度** | 中 | 中 | 🟡 中等 | 选择成熟组件,避免边缘技术 | +| **性能不达标** | 低 | 高 | 🟡 中等 | 性能测试 + 优化 + 必要时回退 | +| **第三方库兼容** | 中 | 低 | 🟢 低 | 严格测试 + 版本锁定 | +| **长期维护** | 中 | 中 | 🟡 中等 | 完善文档 + 规范 + 团队建设 | + +### 5.2 核心风险深度分析 + +#### 5.2.1 事务一致性(严重) + +**问题描述**: +- R2DBC 的事务管理与 JDBC 有本质差异 +- 跨服务事务处理复杂 +- 并发场景下的数据一致性难以保证 + +**缓解策略**: + +1. **单服务事务**:使用 R2DBC 的 `@Transactional` 注解 +2. **跨服务事务**:使用 Saga 模式 +3. **并发控制**:使用分布式锁 + 乐观锁 + +#### 5.2.2 团队技能不足(严重) + +**问题描述**: +- 响应式编程学习曲线陡峭 +- 团队缺乏实战经验 +- 可能产生大量技术债务 + +**缓解策略**: + +1. **培训计划**(4-6 周) + - Week 1-2:响应式编程基础理论 + - Week 3-4:WebFlux + R2DBC 实战 + - Week 5-6:性能优化与调试技巧 + +2. **代码审查**(100% 覆盖) + - 响应式编程规范检查 + - 性能瓶颈识别 + - 最佳实践验证 + +3. **技术分享**(每周 1 次) + - 响应式编程最佳实践 + - 常见问题与解决方案 + - 性能优化案例 + +4. **结对编程**(关键模块) + - 核心模块由经验丰富的开发者主导 + - 新手通过结对学习 + +#### 5.2.3 调试困难(中等) + +**问题描述**: +- 异步代码调试复杂 +- 错误堆栈不直观 +- 性能瓶颈难以定位 + +**缓解策略**: + +1. **启用 Reactor Debug 模式** +2. **完善日志体系** +3. **性能监控** +4. **专项测试** + +--- + +## 六、业务需求匹配度分析 + +### 6.1 核心业务场景评估 + +| 业务场景 | 并发需求 | 响应时间要求 | WebFlux 适用性 | 优先级 | +|---------|---------|-------------|---------------|-------| +| **会员注册** | 低(10-50/s) | < 2s | ⭐⭐⭐ | 低 | +| **会员查询** | 高(200-500/s) | < 500ms | ⭐⭐⭐⭐⭐ | 高 | +| **团课预约** | 高(100-300/s) | < 1s | ⭐⭐⭐⭐⭐ | 高 | +| **私教预约** | 中(50-100/s) | < 1s | ⭐⭐⭐⭐ | 中 | +| **扫码签到** | 极高(500-1000/s) | < 500ms | ⭐⭐⭐⭐⭐ | 极高 | +| **人脸识别签到** | 高(200-500/s) | < 1s | ⭐⭐⭐⭐ | 高 | +| **数据统计** | 中(50-100/s) | < 2s | ⭐⭐⭐⭐ | 中 | +| **营销活动** | 中(50-100/s) | < 1s | ⭐⭐⭐⭐ | 中 | + +**结论**:核心业务场景(查询、预约、签到)非常适合 WebFlux + R2DBC + +### 6.2 非功能性需求评估 + +| 需求 | 要求 | WebFlux + R2DBC | 匹配度 | +|------|------|-----------------|-------| +| **高可用性** | 99.9% | ✅ 支持优雅降级、熔断 | ⭐⭐⭐⭐⭐ | +| **高性能** | 1000 QPS | ✅ 轻松达到 5000+ QPS | ⭐⭐⭐⭐⭐ | +| **低延迟** | P99 < 500ms | ✅ 可达到 200-400ms | ⭐⭐⭐⭐⭐ | +| **可扩展性** | 水平扩展 | ✅ 无状态设计,易于扩展 | ⭐⭐⭐⭐⭐ | +| **可观测性** | 完善监控 | ✅ Micrometer + Actuator | ⭐⭐⭐⭐ | +| **安全性** | 金融级 | ✅ Spring Security Reactive | ⭐⭐⭐⭐⭐ | +| **易维护性** | 低维护成本 | ⚠️ 需要团队技能 | ⭐⭐⭐ | + +--- + +## 七、综合评分 + +### 7.1 评分标准 + +| 评估维度 | 权重 | 得分 | 加权得分 | +|---------|------|------|---------| +| **性能** | 25% | 95 | 23.75 | +| **成本** | 20% | 85 | 17.00 | +| **风险** | 20% | 70 | 14.00 | +| **业务匹配度** | 15% | 95 | 14.25 | +| **技术成熟度** | 10% | 85 | 8.50 | +| **团队能力** | 10% | 60 | 6.00 | +| **总分** | 100% | - | **83.50** | + +**结论**:83.50 分(优秀) + +### 7.2 评分说明 + +- **性能(95 分)**:响应式编程性能优势明显,并发能力提升 10 倍 +- **成本(85 分)**:开发成本增加 40-60%,但运维成本降低 40-50% +- **风险(70 分)**:存在事务一致性、团队技能等风险,但有缓解策略 +- **业务匹配度(95 分)**:核心业务场景非常适合响应式架构 +- **技术成熟度(85 分)**:技术栈成熟,社区活跃,文档完善 +- **团队能力(60 分)**:需要培训和学习,但可以通过培训提升 + +--- + +## 八、最终建议 + +### 8.1 技术选型建议 + +✅ **强烈推荐采用单体应用 + WebFlux + R2DBC + Docker Compose 部署** + +**理由**: + +1. **适合当前规模**:1000 并发用户,3-5 人团队 +2. **开发效率高**:团队上手快,学习成本低 +3. **部署简单**:Docker Compose 一键部署 +4. **性能优秀**:无服务间调用开销,本地事务性能好 +5. **成本低**:开发成本增加 40-60%,但运维成本降低 40-50% +6. **扩展性好**:未来可以平滑演进到微服务 + +### 8.2 关键成功因素 + +1. ✅ 模块化设计(单体内部模块化) +2. ✅ 响应式编程规范(严格遵守规范) +3. ✅ 监控体系(Prometheus + Grafana) +4. ✅ 自动化部署(Docker Compose) +5. ✅ 性能测试(定期性能测试) + +### 8.3 风险控制 + +1. ✅ 分阶段实施(基础设施 → 核心模块 → 高级功能) +2. ✅ 性能基准测试(每个阶段) +3. ✅ 回退方案(必要时可回退到 Spring MVC) +4. ✅ 持续优化(性能、稳定性) + +### 8.4 实施路线图 + +#### 阶段一:基础设施搭建(1-2 周) + +**任务清单**: +1. ✅ 创建 Spring Boot 3.x 项目 +2. ✅ 配置 R2DBC + PostgreSQL +3. ✅ 配置 Redis Reactive +4. ✅ 配置 Actuator + Micrometer +5. ✅ 搭建基础代码结构 +6. ✅ 编写响应式编程规范文档 + +#### 阶段二:核心模块开发(4-6 周) + +**任务清单**: +1. ✅ 会员模块(注册、查询、会员卡管理) +2. ✅ 预约模块(团课预约、私教预约) +3. ✅ 签到模块(扫码签到、人脸识别) +4. ✅ 权益模块(权益扣减、权益记录) +5. ✅ 配置模块(租户配置、门店配置) + +#### 阶段三:高级功能开发(4-6 周) + +**任务清单**: +1. ✅ 订阅模块(模块订阅、计费) +2. ✅ 营销模块(营销活动、推荐奖励) +3. ✅ 数据分析模块(统计报表) +4. ✅ AI 智能模块(运营建议) + +#### 阶段四:测试与优化(2-4 周) + +**任务清单**: +1. ✅ 单元测试(覆盖率 ≥ 80%) +2. ✅ 集成测试 +3. ✅ 性能测试 +4. ✅ 压力测试 +5. ✅ 安全测试 + +--- + +## 九、总结 + +### 9.1 技术架构优势 + +✅ **高性能** +- 响应式编程,并发能力提升 10 倍 +- 响应时间降低 50% +- 资源利用率提升 75% + +✅ **高可用** +- Docker Compose 一键部署 +- 健康检查 + 自动重启 +- 负载均衡 + 故障转移 + +✅ **易维护** +- 单体应用,开发效率高 +- 模块化设计,易于扩展 +- 完善的监控体系 + +✅ **低成本** +- 开发成本增加 40-60%,但运维成本降低 40-50% +- 服务器资源需求低 +- 快速上线 + +### 9.2 关键成功因素 + +1. ✅ 严格遵守响应式编程规范 +2. ✅ 重视事务一致性和并发控制 +3. ✅ 建立完善的监控和调试体系 +4. ✅ 持续的团队培训和代码审查 +5. ✅ 渐进式开发,小步快跑 + +### 9.3 未来演进路径 + +**阶段一:单体应用(当前)** +- 模块化设计 +- Docker Compose 部署 +- 性能优化 + +**阶段二:垂直扩展(6-12 个月)** +- 增加服务器资源 +- 优化数据库性能 +- 引入缓存策略 + +**阶段三:水平扩展(12-24 个月)** +- 多实例部署 +- 负载均衡 +- 数据库读写分离 + +**阶段四:微服务(24-36 个月)** +- 按模块拆分服务 +- 服务注册发现 +- 分布式事务 + +### 9.4 文档清单 + +1. ✅ 《健身房管理系统技术架构设计文档》 GYM-HLD-TECH-001 +2. ✅ 《健身房管理系统响应式编程规范文档》 GYM-STD-REACTIVE-001 +3. ✅ 《健身房管理系统部署运维文档》 GYM-OPS-DEPLOY-001 +4. ✅ 《健身房管理系统技术架构评估总结报告》 GYM-EVAL-TECH-001 + +--- + +## 十、附录 + +### 10.1 参考文档 + +- Spring Boot 3 官方文档 +- Spring WebFlux 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 +- Docker 官方文档 +- Docker Compose 官方文档 +- Prometheus 官方文档 +- Grafana 官方文档 + +### 10.2 技术支持 + +- Spring 社区:https://spring.io/community +- R2DBC 社区:https://r2dbc.io/ +- PostgreSQL 社区:https://www.postgresql.org/community/ +- Docker 社区:https://www.docker.com/community + +### 10.3 联系方式 + +- 技术负责人:张翔 +- 邮箱:zhangxiang@example.com +- 文档版本:v1.0 +- 最后更新:2026-03-04 diff --git a/docs/design/HLD-付费订阅版系统概要设计.md b/docs/design/HLD-付费订阅版系统概要设计.md new file mode 100644 index 0000000..82d1ba5 --- /dev/null +++ b/docs/design/HLD-付费订阅版系统概要设计.md @@ -0,0 +1,543 @@ +# 健身房管理系统付费订阅版业务概要设计文档(HLD) + +> 文档编号: GYM-HLD-SUBSCRIPTION-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建付费订阅版业务概要设计 | + +--- + +## 一、引言 + +### 1.1 编写目的 + +本文档为健身房管理系统付费订阅版的业务概要设计文档(High-Level Design),旨在: + +1. 从业务层面描述付费订阅版的业务范围、业务流程、业务规则 +2. 为付费订阅版详细设计提供业务指导和约束 +3. 作为产品经理、业务分析师、开发人员的业务参考 + +### 1.2 项目背景 + +健身房管理系统付费订阅版在基础版基础上,提供丰富的增值功能,满足中大型健身房、连锁品牌等复杂场景需求。 + +### 1.3 术语定义 + +| 术语 | 定义 | +| ----------------------------- | ------------------------------------------------ | +| 租户(Tenant) | 系统的多租户架构中的独立业务实体,如一个连锁品牌 | +| 门店(Store) | 租户下的具体经营场所 | +| 会员(Member) | 在门店注册的用户 | +| 权益(Benefit) | 会员卡包含的时长、次数、储值、等级等权益 | +| 可预约资源(Bookable Resource) | 团课、私教、场地、线上课程等可被预约的对象 | +| 时段(Slot) | 资源的可预约时间窗口 | +| 订阅模块(Subscription Module) | 按需订阅的增值功能模块 | +| 配置继承(Configuration Inheritance) | 门店配置继承租户配置的机制 | + +### 1.4 参考文档 + +- 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001 +- 《健身房管理系统业务概要设计文档》 GYM-HLD-001 +- 《订阅与配置模块详细设计文档》 GYM-LLD-004 + +--- + +## 二、业务概述 + +### 2.1 业务目标 + +| 目标维度 | 目标描述 | 成功指标 | +| -------- | ---------------------- | -------------------------------- | +| 用户体验 | 提升会员预约和签到体验 | 预约成功率 ≥ 95%,签到耗时 ≤ 3秒 | +| 运营效率 | 降低人工操作成本 | 人工处理时间减少 50% | +| 数据价值 | 提供数据驱动决策支持 | 数据报表使用率 ≥ 80% | +| 业务增长 | 提升会员留存和增长 | 会员留存率提升 20% | + +### 2.2 用户角色 + +| 角色 | 描述 | 主要功能 | +| ---------- | -------------- | ---------------------------- | +| 会员 | 健身房注册用户 | 预约课程、签到、查看个人信息、参与社区 | +| 教练 | 健身房教练 | 排课、私教预约确认、学员签到、发布线上课程 | +| 前台 | 门店前台人员 | 会员接待、签到辅助、会员管理 | +| 店长 | 门店管理者 | 单店全功能管理、数据查看、营销活动管理 | +| 运营管理员 | 平台运营人员 | 营销活动配置、数据分析、AI运营建议查看 | +| 财务专员 | 财务人员 | 账单管理、财务报表 | +| 超级管理员 | 平台最高权限 | 全平台管理、系统配置 | + +### 2.3 业务范围 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 付费订阅版业务范围 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 基础功能(包含基础版所有功能) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员管理 • 预约管理 • 签到管理 • 数据统计 • 系统管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 订阅与配置管理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 订阅管理 • 配置管理 • 套餐管理 • 计费管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 业务扩展类模块 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 私教管理 • 场地预约 • 线上课程 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 体验升级类模块 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 人脸识别签到 • NFC签到 • 智能储物柜 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 营销增长类模块 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 营销活动 • 会员推荐奖励 • 会员互动社区 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 数据智能类模块 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 高级数据分析 • 智能报表 • AI运营建议 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 营销分析与预测模块 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 营销精算模型 • 促销策略预测 • 促销活动效果预测 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 三、核心业务流程 + +### 3.1 订阅流程 + +#### 3.1.1 业务场景 + +租户管理员通过管理后台订阅增值模块。 + +#### 3.1.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 租户管理 │ → │ 查看订阅 │ → │ 选择订阅 │ → │ 确认订阅 │ → │ 模块立即 │ +│ 员登录 │ │ 套餐 │ │ 模块 │ │ │ │ 启用 │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.1.3 业务规则 + +- 订阅成功后模块立即启用 +- 年付享受最大折扣 +- 支持多种支付方式 +- 订阅成功后发送通知 + +#### 3.1.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 支付失败 | 提示用户重新支付 | +| 支付超时 | 提示用户重新发起支付 | + +--- + +### 3.2 配置继承流程 + +#### 3.2.1 业务场景 + +门店管理员配置门店级参数,可以选择继承租户级配置。 + +#### 3.2.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 门店管理 │ → │ 查看租户 │ → │ 选择继承 │ → │ 配置门店 │ → │ 配置立即 │ +│ 员登录 │ │ 级配置 │ │ 模式 │ │ 级参数 │ │ 生效 │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.2.3 业务规则 + +- 查询优先级:门店配置 → 租户配置 → 默认配置 +- 支持三种继承模式(继承/继承+覆盖/自定义) +- 配置变更后立即生效 +- 配置变更记录版本,支持回滚 + +#### 3.2.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 配置冲突 | 提示用户选择覆盖或合并 | +| 配置无效 | 提示用户重新配置 | + +--- + +### 3.3 私教预约流程 + +#### 3.3.1 业务场景 + +会员通过小程序预约私教课程。 + +#### 3.3.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 会员打开 │ → │ 查看私教 │ → │ 选择私教 │ → │ 确认预约 │ → │ 预约成功 │ +│ 小程序 │ │ 课程列表 │ │ 课程 │ │ │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.3.3 业务规则 + +- 私教预约需提前至少24小时 +- 私教取消需提前至少12小时 +- 私教签到后记录考勤 + +#### 3.3.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 教练时间冲突 | 提示用户选择其他时间 | +| 会员卡权益不足 | 提示用户购买会员卡 | + +--- + +### 3.4 营销活动创建流程 + +#### 3.4.1 业务场景 + +运营管理员通过管理后台创建营销活动。 + +#### 3.4.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 运营管理 │ → │ 创建营销 │ → │ 配置活动 │ → │ 发布活动 │ → │ 活动生效 │ +│ 员登录 │ │ 活动 │ │ 规则 │ │ │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.4.3 业务规则 + +- 营销活动需指定时间、规则、奖励 +- 营销活动发布后不可修改规则 +- 营销活动统计按活动、时间维度 + +#### 3.4.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 活动时间冲突 | 提示用户调整活动时间 | +| 活动规则无效 | 提示用户重新配置 | + +--- + +### 3.5 营销分析与预测流程 + +#### 3.5.1 业务场景 + +运营管理员使用营销精算模型预测促销策略。 + +#### 3.5.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 运营管理 │ → │ 选择营销 │ → │ 配置促销 │ → │ 预测效果 │ → │ 查看预测 │ +│ 员登录 │ │ 精算模型 │ │ 参数 │ │ │ │ 结果 │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.5.3 业务规则 + +- 营销精算模型基于历史数据 +- 促销策略预测提供多种方案 +- 促销活动效果预测基于历史数据 + +#### 3.5.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 历史数据不足 | 提示用户积累更多数据 | +| 预测失败 | 提示用户调整参数 | + +--- + +## 四、核心业务规则 + +### 4.1 订阅管理规则 + +| 规则 | 描述 | +|------|------| +| 订阅生效 | 订阅成功后模块立即启用 | +| 计费周期 | 支持月付、季付、半年付、年付 | +| 试用政策 | 不同模块类型提供不同试用时长 | +| 组合套餐 | 支持组合套餐,享受更多优惠 | + +### 4.2 配置管理规则 + +| 规则 | 描述 | +|------|------| +| 配置继承 | 支持门店配置继承租户配置 | +| 继承模式 | 支持继承、继承+覆盖、自定义三种模式 | +| 配置优先级 | 门店配置 → 租户配置 → 默认配置 | +| 配置版本 | 配置变更记录版本,支持回滚 | + +### 4.3 私教管理规则 + +| 规则 | 描述 | +|------|------| +| 私教预约时间 | 私教预约需提前至少24小时 | +| 私教取消时间 | 私教取消需提前至少12小时 | +| 私教考勤 | 私教签到后记录考勤 | + +### 4.4 营销活动规则 + +| 规则 | 描述 | +|------|------| +| 活动规则 | 营销活动需指定时间、规则、奖励 | +| 活动修改 | 营销活动发布后不可修改规则 | +| 活动统计 | 营销活动统计按活动、时间维度 | + +### 4.5 营销分析与预测规则 + +| 规则 | 描述 | +|------|------| +| 模型基础 | 营销精算模型基于历史数据 | +| 预测方案 | 促销策略预测提供多种方案 | +| 效果预测 | 促销活动效果预测基于历史数据 | + +--- + +## 五、业务场景 + +### 5.1 租户订阅场景 + +**场景描述**: +租户A是一家连锁健身房品牌,想启用私教管理和营销活动模块,租户管理员登录管理后台,查看订阅套餐,选择私教管理模块和营销活动模块,选择年付方式,查看优惠信息,确认订阅,支付成功,模块立即启用,租户开始使用新功能。 + +**业务流程**: + +1. 租户管理员登录管理后台 +2. 查看订阅套餐 +3. 选择订阅模块 +4. 选择计费方式 +5. 查看优惠信息 +6. 确认订阅 +7. 支付成功 +8. 模块立即启用 +9. 开始使用新功能 + +**涉及的业务规则**: + +- 订阅成功后模块立即启用,无需重启 +- 年付享受最大折扣 +- 支持多种支付方式 +- 订阅成功后发送通知 + +--- + +### 5.2 门店配置继承场景 + +**场景描述**: +租户A配置了团课、私教、营销模块,门店1想完全继承租户配置,门店2想在租户配置基础上覆盖签到方式(增加人脸识别),门店3想完全自定义配置。各门店管理员登录管理后台,选择继承模式,配置门店级参数,保存配置,配置立即生效。 + +**业务流程**: + +1. 门店管理员登录管理后台 +2. 查看租户级配置 +3. 选择继承模式(继承/继承+覆盖/自定义) +4. 配置门店级参数 +5. 保存配置 +6. 配置立即生效 +7. 验证配置生效 + +**涉及的业务规则**: + +- 查询优先级:门店配置 → 租户配置 → 默认配置 +- 支持三种继承模式 +- 配置变更后立即生效 +- 配置变更记录版本,支持回滚 + +--- + +### 5.3 私教预约场景 + +**场景描述**: +会员张三想预约私教课程,通过小程序查看私教课程列表,选择教练李四,选择时间,确认预约,预约成功,接收提醒。 + +**业务流程**: + +1. 张三打开小程序 +2. 查看私教课程列表 +3. 选择教练李四 +4. 选择时间 +5. 确认预约 +6. 预约成功 +7. 接收提醒 + +**涉及的业务规则**: + +- 私教预约需提前至少24小时 +- 私教取消需提前至少12小时 +- 私教签到后记录考勤 + +--- + +### 5.4 营销活动创建场景 + +**场景描述**: +运营管理员王五想创建一个新会员注册送月卡的活动,登录管理后台,创建营销活动,配置活动规则(新会员注册送月卡),配置活动奖励(月卡一张),发布活动,活动生效,开始监控活动效果。 + +**业务流程**: + +1. 王五登录管理后台 +2. 创建营销活动 +3. 配置活动规则(新会员注册送月卡) +4. 配置活动奖励(月卡一张) +5. 发布活动 +6. 活动生效 +7. 开始监控活动效果 + +**涉及的业务规则**: + +- 营销活动需指定时间、规则、奖励 +- 营销活动发布后不可修改规则 +- 营销活动统计按活动、时间维度 + +--- + +### 5.5 营销分析与预测场景 + +**场景描述**: +运营管理员赵六想预测一个新会员注册送月卡活动的效果,登录管理后台,选择营销精算模型,配置促销参数(活动时间、目标人群、奖励金额),预测活动效果,查看预测结果(预计新增会员数、预计成本、预计收益)。 + +**业务流程**: + +1. 赵六登录管理后台 +2. 选择营销精算模型 +3. 配置促销参数(活动时间、目标人群、奖励金额) +4. 预测活动效果 +5. 查看预测结果(预计新增会员数、预计成本、预计收益) + +**涉及的业务规则**: + +- 营销精算模型基于历史数据 +- 促销策略预测提供多种方案 +- 促销活动效果预测基于历史数据 + +--- + +## 六、数据模型 + +### 6.1 核心实体 + +| 实体 | 描述 | +|------|------| +| 租户(Tenant) | 系统的多租户架构中的独立业务实体 | +| 门店(Store) | 租户下的具体经营场所 | +| 会员(Member) | 在门店注册的用户 | +| 会员卡(MemberCard) | 会员购买的权益卡 | +| 权益(Benefit) | 会员卡包含的权益 | +| 团课(GroupClass) | 集体课程 | +| 私教课程(PrivateClass) | 私教课程 | +| 预约(Booking) | 会员预约记录 | +| 签到(CheckIn) | 会员签到记录 | +| 订阅(Subscription) | 租户订阅记录 | +| 配置(Config) | 租户或门店配置 | +| 营销活动(MarketingActivity) | 营销活动 | +| 营销预测(MarketingPrediction) | 营销预测结果 | + +### 6.2 实体关系 + +``` +租户(Tenant) ──1:N── 门店(Store) +租户(Tenant) ──1:N── 订阅(Subscription) +租户(Tenant) ──1:N── 配置(Config) +门店(Store) ──1:N── 会员(Member) +门店(Store) ──1:N── 配置(Config) +会员(Member) ──1:N── 会员卡(MemberCard) +会员(Member) ──1:N── 预约(Booking) +会员(Member) ──1:N── 签到(CheckIn) +会员卡(MemberCard) ──1:N── 权益(Benefit) +团课(GroupClass) ──1:N── 预约(Booking) +私教课程(PrivateClass) ──1:N── 预约(Booking) +营销活动(MarketingActivity) ──1:N── 营销预测(MarketingPrediction) +``` + +--- + +## 七、技术约束 + +### 7.1 性能约束 + +| 指标 | 要求 | +|------|------| +| API响应时间 | ≤ 500ms | +| 并发用户 | 支持500并发用户 | +| 数据库查询 | 查询响应时间 ≤ 1s | + +### 7.2 可用性约束 + +| 指标 | 要求 | +|------|------| +| 系统可用性 | SLA ≥ 99.9% | +| 故障恢复时间 | MTTR ≤ 30分钟 | + +### 7.3 安全性约束 + +| 指标 | 要求 | +|------|------| +| 数据加密 | 敏感数据加密存储 | +| 访问控制 | 基于角色的访问控制 | +| 操作审计 | 关键操作记录审计日志 | +| 支付安全 | 支持安全支付通道 | + +### 7.4 可扩展性约束 + +| 指标 | 要求 | +|------|------| +| 会员数量 | 不限制 | +| 门店数量 | 支持多门店 | +| 团课容量 | 不限制 | +| 数据保留 | 永久保存 | + +--- + +## 八、附录 + +### 8.1 术语定义 + +| 术语 | 定义 | +|------|------| +| 订阅模块 | 按需订阅的增值功能模块 | +| 私教管理 | 私教课程管理、私教预约、私教签到等功能 | +| 营销活动 | 吸引新会员和提升会员活跃度的活动 | +| 营销精算模型 | 基于历史数据预测促销策略的模型 | +| 促销活动效果预测 | 基于历史数据预测促销活动效果 | +| 配置继承 | 门店配置继承租户配置的机制 | + +### 8.2 参考文档 + +- 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001 +- 《健身房管理系统业务概要设计文档》 GYM-HLD-001 +- 《订阅与配置模块详细设计文档》 GYM-LLD-004 diff --git a/docs/design/HLD-基础版系统概要设计.md b/docs/design/HLD-基础版系统概要设计.md new file mode 100644 index 0000000..a6cb7c9 --- /dev/null +++ b/docs/design/HLD-基础版系统概要设计.md @@ -0,0 +1,474 @@ +# 健身房管理系统基础版业务概要设计文档(HLD) + +> 文档编号: GYM-HLD-BASIC-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建基础版业务概要设计 | + +--- + +## 一、引言 + +### 1.1 编写目的 + +本文档为健身房管理系统基础版的业务概要设计文档(High-Level Design),旨在: + +1. 从业务层面描述基础版的业务范围、业务流程、业务规则 +2. 为基础版详细设计提供业务指导和约束 +3. 作为产品经理、业务分析师、开发人员的业务参考 + +### 1.2 项目背景 + +健身房管理系统基础版是面向小型工作室、个人教练等场景的核心版本,保证业务闭环,提供完整的会员管理、预约、签到等核心功能。 + +### 1.3 术语定义 + +| 术语 | 定义 | +| ----------------------------- | ------------------------------------------------ | +| 租户(Tenant) | 系统的多租户架构中的独立业务实体,如一个连锁品牌 | +| 门店(Store) | 租户下的具体经营场所 | +| 会员(Member) | 在门店注册的用户 | +| 权益(Benefit) | 会员卡包含的时长、次数、储值、等级等权益 | +| 可预约资源(Bookable Resource) | 团课等可被预约的对象 | +| 时段(Slot) | 资源的可预约时间窗口 | + +### 1.4 参考文档 + +- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001 +- 《健身房管理系统业务概要设计文档》 GYM-HLD-001 + +--- + +## 二、业务概述 + +### 2.1 业务目标 + +| 目标维度 | 目标描述 | 成功指标 | +| -------- | ---------------------- | -------------------------------- | +| 用户体验 | 提升会员预约和签到体验 | 预约成功率 ≥ 95%,签到耗时 ≤ 3秒 | +| 运营效率 | 降低人工操作成本 | 人工处理时间减少 50% | +| 数据价值 | 提供基础数据支持 | 数据报表使用率 ≥ 80% | + +### 2.2 用户角色 + +| 角色 | 描述 | 主要功能 | +| ---------- | -------------- | ---------------------------- | +| 会员 | 健身房注册用户 | 预约课程、签到、查看个人信息 | +| 教练 | 健身房教练 | 排课、团课签到管理 | +| 前台 | 门店前台人员 | 会员接待、签到辅助、会员管理 | +| 店长 | 门店管理者 | 单店全功能管理、数据查看 | +| 超级管理员 | 平台最高权限 | 全平台管理、系统配置 | + +### 2.3 业务范围 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 基础版业务范围 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 会员管理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员注册 • 会员卡管理 • 权益管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 预约管理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 团课预约 • 团课管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 签到管理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 扫码签到 • 签到记录管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 数据统计 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 基础数据统计 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 系统管理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 用户管理 • 角色权限管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 三、核心业务流程 + +### 3.1 会员注册流程 + +#### 3.1.1 业务场景 + +新用户通过小程序或前台进行注册,成为健身房会员。 + +#### 3.1.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 用户打开 │ → │ 填写手机 │ → │ 验证手机 │ → │ 填写基本 │ → │ 注册成功 │ +│ 小程序 │ │ 号 │ │ 号 │ │ 信息 │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.1.3 业务规则 + +- 手机号需验证唯一性 +- 手机号需通过短信验证码验证 +- 支持微信授权快速注册 +- 注册成功后自动创建会员档案 + +#### 3.1.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 手机号已存在 | 提示用户直接登录 | +| 验证码错误 | 提示用户重新输入 | +| 验证码过期 | 提示用户重新获取 | + +--- + +### 3.2 团课预约流程 + +#### 3.2.1 业务场景 + +会员通过小程序预约团课,教练通过管理后台创建团课。 + +#### 3.2.2 业务流程 + +**会员预约团课**: + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 会员打开 │ → │ 查看团课 │ → │ 选择团课 │ → │ 确认预约 │ → │ 预约成功 │ +│ 小程序 │ │ 列表 │ │ │ │ │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +**教练创建团课**: + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 教练打开 │ → │ 点击创建 │ → │ 填写团课 │ → │ 发布团课 │ → │ 发布成功 │ +│ 管理后台 │ │ 团课 │ │ 信息 │ │ │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.2.3 业务规则 + +- 预约需在课程开始前至少30分钟 +- 取消预约需在课程开始前至少2小时 +- 每节课最多20人 +- 预约成功后发送提醒 +- 预约成功后扣减权益 +- 团课需指定教练、时间、地点 +- 团课取消需提前24小时通知 +- 团课取消后自动退款 + +#### 3.2.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 课程已满 | 提示用户选择其他课程 | +| 会员卡权益不足 | 提示用户购买会员卡 | +| 预约时间过短 | 提示用户提前预约 | + +--- + +### 3.3 签到流程 + +#### 3.3.1 业务场景 + +会员到店后通过扫码进行签到,记录到店信息。 + +#### 3.3.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 会员到店 │ → │ 扫描签到 │ → │ 验证会员 │ → │ 签到成功 │ → │ 记录到店 │ +│ │ │ 码 │ │ 卡 │ │ │ │ 时间 │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.3.3 业务规则 + +- 签到需验证会员卡有效性 +- 签到需验证预约信息(如有) +- 签到成功后记录到店时间 +- 签到失败后提示原因 + +#### 3.3.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 会员卡无效 | 提示用户购买会员卡 | +| 会员卡过期 | 提示用户续费 | +| 签到码无效 | 提示用户重新扫描 | + +--- + +### 3.4 会员卡购买流程 + +#### 3.4.1 业务场景 + +会员通过小程序购买会员卡,获得相应权益。 + +#### 3.4.2 业务流程 + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 会员打开 │ → │ 查看会员 │ → │ 选择会员 │ → │ 确认购买 │ → │ 购买成功 │ +│ 小程序 │ │ 卡列表 │ │ 卡 │ │ │ │ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ +``` + +#### 3.4.3 业务规则 + +- 支持时长卡、次卡、储值卡 +- 会员卡到期前7天提醒 +- 会员卡续费后权益立即生效 +- 会员卡使用记录永久保存 + +#### 3.4.4 异常处理 + +| 异常场景 | 处理方式 | +|---------|---------| +| 支付失败 | 提示用户重新支付 | +| 支付超时 | 提示用户重新发起支付 | + +--- + +## 四、核心业务规则 + +### 4.1 会员管理规则 + +| 规则 | 描述 | +|------|------| +| 会员唯一性 | 手机号作为会员唯一标识 | +| 会员信息完整性 | 必填字段:手机号、姓名、性别 | +| 会员信息修改权限 | 会员只能编辑自己的基本信息,前台和店长可以编辑所有信息 | + +### 4.2 会员卡管理规则 + +| 规则 | 描述 | +|------|------| +| 会员卡类型 | 支持时长卡、次卡、储值卡 | +| 会员卡有效期 | 时长卡有有效期,次卡和储值卡无有效期 | +| 会员卡到期提醒 | 到期前7天提醒 | +| 会员卡续费 | 续费后权益立即生效 | + +### 4.3 预约管理规则 + +| 规则 | 描述 | +|------|------| +| 预约时间限制 | 预约需在课程开始前至少30分钟 | +| 取消预约时间限制 | 取消预约需在课程开始前至少2小时 | +| 团课容量限制 | 每节课最多20人 | +| 预约权益扣减 | 预约成功后扣减权益 | + +### 4.4 签到管理规则 + +| 规则 | 描述 | +|------|------| +| 签到验证 | 签到需验证会员卡有效性 | +| 签到预约验证 | 签到需验证预约信息(如有) | +| 签到记录 | 签到成功后记录到店时间 | + +### 4.5 数据统计规则 + +| 规则 | 描述 | +|------|------| +| 数据保留期限 | 数据保留30天 | +| 统计维度 | 支持按日、周、月统计 | +| 数据导出 | 支持数据导出 | + +--- + +## 五、业务场景 + +### 5.1 会员注册场景 + +**场景描述**: +新用户张三通过小程序注册成为健身房会员。 + +**业务流程**: + +1. 张三打开小程序 +2. 点击注册 +3. 填写手机号 +4. 验证手机号 +5. 填写基本信息(姓名、性别、生日、身高体重、健身目标) +6. 注册成功 +7. 自动创建会员档案 + +**涉及的业务规则**: + +- 手机号需验证唯一性 +- 手机号需通过短信验证码验证 +- 注册成功后自动创建会员档案 + +--- + +### 5.2 团课预约场景 + +**场景描述**: +会员李四通过小程序预约团课。 + +**业务流程**: + +1. 李四打开小程序 +2. 查看团课列表 +3. 选择团课 +4. 查看详情 +5. 确认预约 +6. 预约成功 +7. 接收提醒 + +**涉及的业务规则**: + +- 预约需在课程开始前至少30分钟 +- 取消预约需在课程开始前至少2小时 +- 每节课最多20人 +- 预约成功后发送提醒 +- 预约成功后扣减权益 + +--- + +### 5.3 签到场景 + +**场景描述**: +会员王五到店后通过扫码进行签到。 + +**业务流程**: + +1. 王五到店 +2. 扫描签到码 +3. 验证会员卡 +4. 签到成功 +5. 记录到店时间 + +**涉及的业务规则**: + +- 签到需验证会员卡有效性 +- 签到需验证预约信息(如有) +- 签到成功后记录到店时间 +- 签到失败后提示原因 + +--- + +### 5.4 会员卡购买场景 + +**场景描述**: +会员赵六通过小程序购买会员卡。 + +**业务流程**: + +1. 赵六打开小程序 +2. 查看会员卡列表 +3. 选择会员卡 +4. 确认购买 +5. 购买成功 + +**涉及的业务规则**: + +- 支持时长卡、次卡、储值卡 +- 会员卡到期前7天提醒 +- 会员卡续费后权益立即生效 + +--- + +## 六、数据模型 + +### 6.1 核心实体 + +| 实体 | 描述 | +|------|------| +| 会员(Member) | 健身房注册用户 | +| 会员卡(MemberCard) | 会员购买的权益卡 | +| 权益(Benefit) | 会员卡包含的权益 | +| 团课(GroupClass) | 集体课程 | +| 预约(Booking) | 会员预约记录 | +| 签到(CheckIn) | 会员签到记录 | + +### 6.2 实体关系 + +``` +会员(Member) ──1:N── 会员卡(MemberCard) +会员(Member) ──1:N── 预约(Booking) +会员(Member) ──1:N── 签到(CheckIn) +会员卡(MemberCard) ──1:N── 权益(Benefit) +团课(GroupClass) ──1:N── 预约(Booking) +``` + +--- + +## 七、技术约束 + +### 7.1 性能约束 + +| 指标 | 要求 | +|------|------| +| API响应时间 (P99) | 200-400ms | +| 并发用户 | 支持1000并发用户 | +| 吞吐量 (QPS) | 3000-5000 | +| 数据库查询 | 查询响应时间 ≤ 500ms | + +### 7.2 可用性约束 + +| 指标 | 要求 | +|------|------| +| 系统可用性 | SLA ≥ 99.9% | +| 故障恢复时间 | MTTR ≤ 30分钟 | + +### 7.3 安全性约束 + +| 指标 | 要求 | +|------|------| +| 数据加密 | 敏感数据加密存储 | +| 访问控制 | 基于角色的访问控制 | +| 操作审计 | 关键操作记录审计日志 | + +### 7.4 可扩展性约束 + +| 指标 | 要求 | +|------|------| +| 会员数量 | 最多500人 | +| 门店数量 | 单门店 | +| 团课容量 | 每节课最多20人 | +| 数据保留 | 保留30天 | + +--- + +## 八、附录 + +### 8.1 术语定义 + +| 术语 | 定义 | +|------|------| +| 会员 | 在健身房注册的用户 | +| 会员卡 | 会员购买的权益卡,包括时长卡、次卡、储值卡 | +| 权益 | 会员卡包含的时长、次数、储值、等级等权益 | +| 团课 | 集体课程,由教练带领多个会员一起上课 | +| 预约 | 会员预约团课 | +| 签到 | 会员到店记录 | + +### 8.2 参考文档 + +- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001 +- 《健身房管理系统业务概要设计文档》 GYM-HLD-001 diff --git a/docs/design/HLD-技术架构设计.md b/docs/design/HLD-技术架构设计.md new file mode 100644 index 0000000..97b5986 --- /dev/null +++ b/docs/design/HLD-技术架构设计.md @@ -0,0 +1,1311 @@ +# 健身房管理系统技术架构设计文档 + +> 文档编号: GYM-HLD-TECH-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建技术架构设计文档 | + +--- + +## 参考文档 + +- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001 +- 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001 +- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001 +- 《健身房管理系统付费订阅版业务概要设计文档》 GYM-HLD-SUBSCRIPTION-001 +- Spring Boot 3 官方文档 +- Spring WebFlux 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 + +--- + +## 一、架构决策 + +### 1.1 架构选型 + +经过深入评估,本系统采用以下架构决策: + +| 决策项 | 选择方案 | 理由 | +|-------|---------|------| +| **应用架构** | 单体应用 | 适合当前规模(1000 并发用户),开发效率高,部署简单,成本低 | +| **编程模型** | 响应式编程(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 总体架构 + +采用分层架构 + 模块化设计的单体应用: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 单体应用总体架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 客户端层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员小程序 (uniapp+Vue3) │ │ +│ │ • 教练端App (uniapp+Vue3) │ │ +│ │ • 管理后台PC (Vue3+Vite) │ │ +│ │ • 硬件设备 (人脸/NFC) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Nginx 反向代理 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 负载均衡 • SSL 终止 • 静态资源 • 限流 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Presentation Layer (WebFlux) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Controller • Router • Filter • Validator │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Application Layer (业务编排) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Service • Facade • Orchestrator • 事务管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Domain Layer (领域模型) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Entity • Value Object • Domain Service • Repository │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Infrastructure Layer (基础设施) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Repository (R2DBC) • Cache (Redis) │ │ +│ │ • Message (RabbitMQ) • Search (Elasticsearch) │ │ +│ │ • File (OSS) • Distributed Lock │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 外部服务层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • PostgreSQL • Redis • RabbitMQ • Elasticsearch │ │ +│ │ • 微信开放平台 • 短信服务 • 支付服务 • OSS存储 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 监控与运维层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Prometheus • Grafana • 日志收集 • 告警 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 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. **链式调用** + - 使用 `flatMap`、`map`、`filter` 等操作符链式调用 + - 避免嵌套的 `subscribe` + +3. **错误处理** + - 使用 `onErrorResume`、`onErrorReturn` 处理错误 + - 避免使用 `try-catch` 捕获响应式异常 + +4. **背压处理** + - 使用 `onBackpressureBuffer`、`onBackpressureDrop` 处理背压 + - 避免内存溢出 + +#### 3.2.2 代码示例 + +**✅ 正确示例**: + +```java +public Mono 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)); +} +``` + +**❌ 错误示例**: + +```java +public Member getMember(Long id) { + // 错误:使用 block() 阻塞 + return memberRepository.findById(id).block(); +} + +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> { + // 错误:在 flatMap 中使用 block() + List cards = memberCardRepository.findByMemberId(member.getId()).collectList().block(); + return Mono.just(member); + }); +} +``` + +### 3.3 响应式事务管理 + +#### 3.3.1 本地事务 + +使用 `@Transactional` 注解管理本地事务: + +```java +@Service +public class BookingService { + + @Transactional + public Mono 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 实现分布式锁: + +```java +@Component +public class RedisDistributedLock { + + private final ReactiveRedisTemplate redisTemplate; + private static final String LOCK_PREFIX = "lock:"; + private static final long DEFAULT_EXPIRE_TIME = 30; + + public Mono 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 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 模式: + +```java +@Service +public class BookingSaga { + + public Mono execute(BookingRequest request) { + return bookSlot(request) + .flatMap(booking -> sendNotification(booking)) + .flatMap(booking -> updateStatistics(booking)) + .onErrorResume(e -> compensate(request, e)); + } + + private Mono bookSlot(BookingRequest request) { + // 预约逻辑 + } + + private Mono sendNotification(BookingRecord booking) { + // 发送通知 + } + + private Mono updateStatistics(BookingRecord booking) { + // 更新统计 + } + + private Mono compensate(BookingRequest request, Throwable e) { + // 补偿逻辑 + } +} +``` + +--- + +## 四、部署架构 + +### 4.1 Docker Compose 部署 + +#### 4.1.1 完整的 docker-compose.yml + +```yaml +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 + +```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 部署流程 + +```bash +# 1. 克隆代码 +git clone +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 + +```yaml +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 配置 + +```yaml +# 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 + +```json +{ + "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 告警配置 + +```yaml +# 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 日志配置 + +```yaml +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 结构化日志 + +```java +@Slf4j +public class MemberService { + + public Mono 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 索引优化 + +```sql +-- 会员表索引 +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 查询优化 + +```java +// ✅ 正确:使用索引 +public Flux listMembers(Long tenantId, Long storeId) { + return memberRepository.findByTenantIdAndStoreId(tenantId, storeId); +} + +// ❌ 错误:全表扫描 +public Flux 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 多级缓存 + +```java +@Service +public class MemberService { + + private final MemberRepository memberRepository; + private final ReactiveRedisTemplate redisTemplate; + + public Mono 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 连接池配置 + +```yaml +spring: + r2dbc: + pool: + initial-size: 5 # 初始连接数 + max-size: 20 # 最大连接数 + max-idle-time: 30m # 最大空闲时间 + max-life-time: 1h # 最大生命周期 + acquire-timeout: 5s # 获取连接超时时间 +``` + +#### 6.3.2 Redis 连接池配置 + +```yaml +spring: + data: + redis: + lettuce: + pool: + max-active: 20 # 最大连接数 + max-idle: 10 # 最大空闲连接数 + min-idle: 5 # 最小空闲连接数 +``` + +--- + +## 七、安全设计 + +### 7.1 认证授权 + +#### 7.1.1 JWT 认证 + +```java +@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 数据加密 + +```java +@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 数据脱敏 + +```java +@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 单元测试 + +```java +@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 集成测试 + +```java +@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 性能测试 + +```java +@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 个月)** +- 按模块拆分服务 +- 服务注册发现 +- 分布式事务 diff --git a/docs/design/LLD-付费订阅版系统详细设计.md b/docs/design/LLD-付费订阅版系统详细设计.md new file mode 100644 index 0000000..03be989 --- /dev/null +++ b/docs/design/LLD-付费订阅版系统详细设计.md @@ -0,0 +1,1344 @@ +# 健身房管理系统付费订阅版详细设计文档(LLD) + +> 文档编号: GYM-LLD-SUBSCRIPTION-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-04 | 张翔 | 创建付费订阅版详细设计 | + +--- + +## 参考文档 + +- 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001 +- 《健身房管理系统付费订阅版业务概要设计文档》 GYM-HLD-SUBSCRIPTION-001 +- 《健身房管理系统详细设计文档》 GYM-LLD-000 +- 《订阅与配置模块详细设计文档》 GYM-LLD-004 +- Spring Boot 3 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 + +--- + +## 一、系统架构设计 + +### 1.1 总体架构 + +采用分层架构 + 模块化设计的单体应用: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 付费订阅版单体应用架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 客户端层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员小程序 (uniapp+Vue3) │ │ +│ │ • 教练端App (uniapp+Vue3) │ │ +│ │ • 管理后台PC (Vue3+Vite) │ │ +│ │ • 硬件设备 (人脸/NFC) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Presentation Layer (WebFlux) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Controller • Router • Filter • Validator │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Application Layer (业务编排) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Service • Facade • Orchestrator • 事务管理 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Domain Layer (领域模型) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Entity • Value Object • Domain Service • Repository │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Infrastructure Layer (基础设施) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Repository (R2DBC) • Cache (Redis) │ │ +│ │ • Message (RabbitMQ) • Search (Elasticsearch) │ │ +│ │ • File (OSS) • Distributed Lock │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 外部服务层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • PostgreSQL • Redis • RabbitMQ • Elasticsearch │ │ +│ │ • 微信开放平台 • 短信服务 • 支付服务 • OSS存储 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、订阅与配置模块设计 + +### 2.1 模块概述 + +订阅与配置模块是付费订阅版的核心基础设施模块,负责: + +- 产品版本管理(基础版 + 订阅模块) +- 租户级和门店级配置管理 +- 配置继承与覆盖机制 +- 订阅计费与生命周期管理 + +### 2.2 数据模型设计 + +**租户模块配置表 (tenant_module_config)** + +```sql +CREATE TABLE tenant_module_config ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + module_code VARCHAR(32) NOT NULL, + enabled BOOLEAN NOT NULL, + config_data JSONB, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_tenant_module UNIQUE (tenant_id, module_code), + CONSTRAINT fk_tenant_module_config FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +**门店模块配置表 (store_module_config)** + +```sql +CREATE TABLE store_module_config ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + module_code VARCHAR(32) NOT NULL, + inherit_mode SMALLINT NOT NULL, + enabled BOOLEAN NOT NULL, + config_data JSONB, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_store_module UNIQUE (store_id, module_code), + CONSTRAINT fk_store_module_config FOREIGN KEY (store_id) REFERENCES store(id) +); +``` + +**订阅记录表 (subscription_record)** + +```sql +CREATE TABLE subscription_record ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + subscription_no VARCHAR(32) NOT NULL, + module_code VARCHAR(32) NOT NULL, + billing_cycle SMALLINT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + discount_amount DECIMAL(10,2) DEFAULT 0.00, + actual_amount DECIMAL(10,2) NOT NULL, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_subscription_no UNIQUE (subscription_no), + CONSTRAINT fk_subscription_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +### 2.3 核心服务设计 + +**配置查询服务** + +```java +@Service +@Slf4j +@RequiredArgsConstructor +public class ConfigQueryService { + + private final TenantModuleConfigRepository tenantModuleConfigRepository; + private final StoreModuleConfigRepository storeModuleConfigRepository; + private final ConfigMerger configMerger; + private final ReactiveRedisTemplate redisTemplate; + + private static final String CACHE_PREFIX = "config:"; + private static final Duration CACHE_TTL = Duration.ofMinutes(30); + + /** + * 获取模块配置(门店 → 租户 → 默认) + */ + public Mono getModuleConfig(Long tenantId, Long storeId, String moduleCode) { + String cacheKey = buildCacheKey(tenantId, storeId, moduleCode); + + return redisTemplate.opsForValue().get(cacheKey) + .switchIfEmpty(Mono.defer(() -> loadModuleConfig(tenantId, storeId, moduleCode)) + .flatMap(config -> redisTemplate.opsForValue() + .set(cacheKey, config, CACHE_TTL) + .thenReturn(config))) + .doOnSuccess(config -> log.debug("获取模块配置成功: tenantId={}, storeId={}, moduleCode={}", + tenantId, storeId, moduleCode)) + .doOnError(e -> log.error("获取模块配置失败: tenantId={}, storeId={}, moduleCode={}", + tenantId, storeId, moduleCode, e)); + } + + /** + * 加载模块配置 + */ + private Mono loadModuleConfig(Long tenantId, Long storeId, String moduleCode) { + return tenantModuleConfigRepository + .findByTenantIdAndModuleCode(tenantId, moduleCode) + .switchIfEmpty(Mono.just(getDefaultModuleConfig(moduleCode))) + .flatMap(tenantConfig -> storeModuleConfigRepository + .findByStoreIdAndModuleCode(storeId, moduleCode) + .map(storeConfig -> configMerger.mergeConfig(tenantConfig, storeConfig)) + .defaultIfEmpty(tenantConfig)); + } + + /** + * 构建缓存Key + */ + private String buildCacheKey(Long tenantId, Long storeId, String moduleCode) { + return String.format("%s%d:%d:%s", CACHE_PREFIX, tenantId, storeId, moduleCode); + } + + /** + * 获取默认模块配置 + */ + private ModuleConfig getDefaultModuleConfig(String moduleCode) { + return ModuleConfig.builder() + .moduleCode(moduleCode) + .enabled(false) + .configData(new HashMap<>()) + .build(); + } +} +``` + +**配置合并服务** + +```java +@Service +public class ConfigMerger { + + /** + * 合并配置 + */ + public ModuleConfig mergeConfig(ModuleConfig tenantConfig, StoreModuleConfig storeConfig) { + if (storeConfig.getInheritMode() == 1) { + return tenantConfig; + } + + if (storeConfig.getInheritMode() == 2) { + Map mergedData = new HashMap<>(tenantConfig.getConfigData()); + mergedData.putAll(storeConfig.getConfigData()); + + return ModuleConfig.builder() + .moduleCode(tenantConfig.getModuleCode()) + .enabled(storeConfig.isEnabled()) + .configData(mergedData) + .build(); + } + + return ModuleConfig.builder() + .moduleCode(tenantConfig.getModuleCode()) + .enabled(storeConfig.isEnabled()) + .configData(storeConfig.getConfigData()) + .build(); + } +} +``` + +**订阅服务** + +```java +@Service +@Slf4j +@RequiredArgsConstructor +public class SubscriptionService { + + private final SubscriptionRecordRepository subscriptionRecordRepository; + private final TenantModuleConfigRepository tenantModuleConfigRepository; + private final PaymentService paymentService; + private final MessageService messageService; + + /** + * 订阅模块 + */ + @Transactional + public Mono subscribe(SubscriptionRequest request) { + return validateSubscription(request) + .flatMap(v -> paymentService.createPayment(request)) + .flatMap(payment -> createSubscriptionRecord(request)) + .flatMap(record -> enableModule(request.getTenantId(), request.getModuleCode()) + .thenReturn(record)) + .flatMap(record -> messageService.sendSubscriptionNotification(record) + .thenReturn(record)) + .doOnSuccess(record -> log.info("订阅成功: subscriptionNo={}", record.getSubscriptionNo())) + .doOnError(e -> log.error("订阅失败: {}", e.getMessage())); + } + + /** + * 验证订阅 + */ + private Mono validateSubscription(SubscriptionRequest request) { + return subscriptionRecordRepository + .existsActiveSubscription(request.getTenantId(), request.getModuleCode()) + .flatMap(exists -> { + if (Boolean.TRUE.equals(exists)) { + return Mono.error(new BusinessException("该模块已订阅")); + } + return Mono.empty(); + }); + } + + /** + * 创建订阅记录 + */ + private Mono createSubscriptionRecord(SubscriptionRequest request) { + SubscriptionRecord record = SubscriptionRecord.builder() + .tenantId(request.getTenantId()) + .subscriptionNo(generateSubscriptionNo(request.getTenantId())) + .moduleCode(request.getModuleCode()) + .billingCycle(request.getBillingCycle()) + .amount(request.getAmount()) + .discountAmount(request.getDiscountAmount()) + .actualAmount(request.getActualAmount()) + .startDate(LocalDate.now()) + .endDate(calculateEndDate(request.getBillingCycle())) + .status(1) + .build(); + + return subscriptionRecordRepository.save(record); + } + + /** + * 计算结束日期 + */ + private LocalDate calculateEndDate(Integer billingCycle) { + switch (billingCycle) { + case 1: + return LocalDate.now().plusMonths(1); + case 2: + return LocalDate.now().plusMonths(3); + case 3: + return LocalDate.now().plusMonths(6); + case 4: + return LocalDate.now().plusYears(1).plusMonths(1); + default: + return LocalDate.now().plusMonths(1); + } + } + + /** + * 启用模块 + */ + private Mono enableModule(Long tenantId, String moduleCode) { + return tenantModuleConfigRepository + .findByTenantIdAndModuleCode(tenantId, moduleCode) + .switchIfEmpty(Mono.just(new TenantModuleConfig())) + .flatMap(config -> { + config.setTenantId(tenantId); + config.setModuleCode(moduleCode); + config.setEnabled(true); + config.setConfigData(new HashMap<>()); + config.setVersion(config.getVersion() + 1); + + return tenantModuleConfigRepository.save(config); + }) + .then(); + } + + /** + * 生成订阅号 + */ + private String generateSubscriptionNo(Long tenantId) { + String prefix = "S" + tenantId; + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = String.valueOf(new Random().nextInt(1000)); + return prefix + timestamp.substring(timestamp.length() - 8) + random; + } +} +``` + +--- + +## 三、业务扩展类模块设计 + +### 3.1 私教管理模块 + +#### 3.1.1 数据模型设计 + +**私教课程表 (private_class)** + +```sql +CREATE TABLE private_class ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + coach_id BIGINT NOT NULL, + member_id BIGINT, + class_date DATE NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + duration INT NOT NULL, + price DECIMAL(10,2) NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_private_class_coach FOREIGN KEY (coach_id) REFERENCES coach(id), + CONSTRAINT fk_private_class_member FOREIGN KEY (member_id) REFERENCES member(id) +); +``` + +#### 3.1.2 核心服务设计 + +**私教预约服务** + +```java +@Service +@Slf4j +@RequiredArgsConstructor +public class PrivateClassBookingService { + + private final PrivateClassRepository privateClassRepository; + private final CoachRepository coachRepository; + private final BenefitService benefitService; + private final ReactiveRedisTemplate redisTemplate; + private final DistributedLockService distributedLockService; + + private static final String LOCK_PREFIX = "lock:private_class:"; + private static final Duration LOCK_TTL = Duration.ofSeconds(30); + + /** + * 预约私教 + */ + @Transactional + public Mono book(PrivateClassBookingRequest request) { + String lockKey = buildLockKey(request.getCoachId(), request.getClassDate(), request.getStartTime()); + + return distributedLockService.acquireLock(lockKey, LOCK_TTL) + .flatMap(lock -> validateBookingTime(request.getClassDate(), request.getStartTime()) + .flatMap(v -> validateCoachAvailability(request.getCoachId(), request.getClassDate(), + request.getStartTime(), request.getEndTime())) + .flatMap(v -> validateMemberBenefit(request.getMemberId())) + .flatMap(v -> coachRepository.findById(request.getCoachId()) + .switchIfEmpty(Mono.error(new BusinessException("教练不存在")))) + .flatMap(coach -> createPrivateClass(coach, request)) + .flatMap(privateClass -> benefitService.deductBenefit(request.getMemberId(), privateClass.getPrice()) + .thenReturn(privateClass)) + .doFinally(signal -> distributedLockService.releaseLock(lockKey))) + .doOnSuccess(privateClass -> log.info("私教预约成功: privateClassId={}", privateClass.getId())) + .doOnError(e -> log.error("私教预约失败: {}", e.getMessage())); + } + + /** + * 验证预约时间 + */ + private Mono validateBookingTime(LocalDate classDate, LocalTime startTime) { + LocalDateTime classDateTime = LocalDateTime.of(classDate, startTime); + if (classDateTime.isBefore(LocalDateTime.now().plusHours(24))) { + return Mono.error(new BusinessException("预约时间需提前至少24小时")); + } + return Mono.empty(); + } + + /** + * 验证教练可用性 + */ + private Mono validateCoachAvailability(Long coachId, LocalDate classDate, + LocalTime startTime, LocalTime endTime) { + return privateClassRepository + .isCoachAvailable(coachId, classDate, startTime, endTime) + .flatMap(isAvailable -> { + if (!Boolean.TRUE.equals(isAvailable)) { + return Mono.error(new BusinessException("教练时间冲突")); + } + return Mono.empty(); + }); + } + + /** + * 验证会员权益 + */ + private Mono validateMemberBenefit(Long memberId) { + return benefitService.hasBenefit(memberId) + .flatMap(hasBenefit -> { + if (!Boolean.TRUE.equals(hasBenefit)) { + return Mono.error(new BusinessException("会员权益不足")); + } + return Mono.empty(); + }); + } + + /** + * 创建私教课程 + */ + private Mono createPrivateClass(Coach coach, PrivateClassBookingRequest request) { + PrivateClass privateClass = PrivateClass.builder() + .tenantId(coach.getTenantId()) + .storeId(coach.getStoreId()) + .coachId(coach.getId()) + .memberId(request.getMemberId()) + .classDate(request.getClassDate()) + .startTime(request.getStartTime()) + .endTime(request.getEndTime()) + .duration(request.getDuration()) + .price(request.getPrice()) + .status(1) + .build(); + + return privateClassRepository.save(privateClass); + } + + /** + * 构建锁Key + */ + private String buildLockKey(Long coachId, LocalDate classDate, LocalTime startTime) { + return String.format("%s%d:%s:%s", LOCK_PREFIX, coachId, classDate, startTime); + } +} +``` + +### 3.2 场地预约模块 + +#### 3.2.1 数据模型设计 + +**场地表 (venue)** + +```sql +CREATE TABLE venue ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(64) NOT NULL, + capacity INT, + area DECIMAL(10,2), + equipment VARCHAR(256), + images JSONB, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_venue_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_venue_store FOREIGN KEY (store_id) REFERENCES store(id) +); +``` + +**场地预约表 (venue_booking)** + +```sql +CREATE TABLE venue_booking ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + venue_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + booking_no VARCHAR(32) NOT NULL, + booking_date DATE NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + duration INT NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_venue_booking_no UNIQUE (booking_no), + CONSTRAINT fk_venue_booking_venue FOREIGN KEY (venue_id) REFERENCES venue(id), + CONSTRAINT fk_venue_booking_member FOREIGN KEY (member_id) REFERENCES member(id) +); +``` + +### 3.3 线上课程模块 + +#### 3.3.1 数据模型设计 + +**线上课程表 (online_course)** + +```sql +CREATE TABLE online_course ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + code VARCHAR(32), + category VARCHAR(64), + description TEXT, + cover_image VARCHAR(512), + video_url VARCHAR(512), + duration INT NOT NULL, + difficulty SMALLINT DEFAULT 1, + calories INT, + equipment VARCHAR(256), + benefits JSONB, + price DECIMAL(10,2), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_online_course_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +--- + +## 四、营销增长类模块设计 + +### 4.1 营销活动模块 + +#### 4.1.1 数据模型设计 + +**营销活动表 (marketing_activity)** + +```sql +CREATE TABLE marketing_activity ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + type SMALLINT NOT NULL, + description TEXT, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + rules JSONB NOT NULL, + rewards JSONB NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_marketing_activity_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +**营销活动参与记录表 (marketing_activity_participant)** + +```sql +CREATE TABLE marketing_activity_participant ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + activity_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + participated_at TIMESTAMP NOT NULL, + reward_status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_activity_participant UNIQUE (activity_id, member_id), + CONSTRAINT fk_activity_participant_activity FOREIGN KEY (activity_id) REFERENCES marketing_activity(id), + CONSTRAINT fk_activity_participant_member FOREIGN KEY (member_id) REFERENCES member(id) +); +``` + +#### 4.1.2 核心服务设计 + +**营销活动服务** + +```java +@Service +public class MarketingActivityService { + + @Autowired + private MarketingActivityRepository marketingActivityRepository; + + @Autowired + private MarketingActivityParticipantRepository participantRepository; + + @Autowired + private BenefitService benefitService; + + /** + * 创建营销活动 + */ + @Transactional + public MarketingActivity createActivity(MarketingActivityCreateRequest request) { + validateActivityTime(request.getStartDate(), request.getEndDate()); + + MarketingActivity activity = new MarketingActivity(); + activity.setTenantId(request.getTenantId()); + activity.setName(request.getName()); + activity.setType(request.getType()); + activity.setDescription(request.getDescription()); + activity.setStartDate(request.getStartDate()); + activity.setEndDate(request.getEndDate()); + activity.setRules(request.getRules()); + activity.setRewards(request.getRewards()); + activity.setStatus(1); + + return marketingActivityRepository.save(activity); + } + + /** + * 参与营销活动 + */ + @Transactional + public MarketingActivityParticipant participate(Long activityId, Long memberId) { + MarketingActivity activity = marketingActivityRepository.findById(activityId) + .orElseThrow(() -> new BusinessException("活动不存在")); + + validateActivityStatus(activity); + validateActivityTime(activity); + validateParticipant(activityId, memberId); + + MarketingActivityParticipant participant = new MarketingActivityParticipant(); + participant.setTenantId(activity.getTenantId()); + participant.setActivityId(activityId); + participant.setMemberId(memberId); + participant.setParticipatedAt(LocalDateTime.now()); + participant.setRewardStatus(1); + + participant = participantRepository.save(participant); + + grantReward(activity, memberId); + + return participant; + } + + /** + * 验证活动时间 + */ + private void validateActivityTime(LocalDate startDate, LocalDate endDate) { + if (startDate.isAfter(endDate)) { + throw new BusinessException("活动开始时间不能晚于结束时间"); + } + } + + /** + * 验证活动状态 + */ + private void validateActivityStatus(MarketingActivity activity) { + if (activity.getStatus() != 1) { + throw new BusinessException("活动未开始或已结束"); + } + } + + /** + * 验证活动时间 + */ + private void validateActivityTime(MarketingActivity activity) { + LocalDate now = LocalDate.now(); + if (now.isBefore(activity.getStartDate()) || now.isAfter(activity.getEndDate())) { + throw new BusinessException("活动未开始或已结束"); + } + } + + /** + * 验证参与者 + */ + private void validateParticipant(Long activityId, Long memberId) { + if (participantRepository.existsByActivityIdAndMemberId(activityId, memberId)) { + throw new BusinessException("已参与过该活动"); + } + } + + /** + * 发放奖励 + */ + private void grantReward(MarketingActivity activity, Long memberId) { + Map rewards = activity.getRewards(); + String rewardType = (String) rewards.get("type"); + Object rewardValue = rewards.get("value"); + + switch (rewardType) { + case "card": + benefitService.grantCard(memberId, (String) rewardValue); + break; + case "points": + benefitService.grantPoints(memberId, (Integer) rewardValue); + break; + case "coupon": + benefitService.grantCoupon(memberId, (String) rewardValue); + break; + default: + throw new BusinessException("不支持的奖励类型"); + } + } +} +``` + +--- + +## 五、数据智能类模块设计 + +### 5.1 高级数据分析模块 + +#### 5.1.1 核心服务设计 + +**高级数据分析服务** + +```java +@Service +public class AdvancedDataAnalysisService { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private BookingRecordRepository bookingRecordRepository; + + @Autowired + private CheckInRecordRepository checkInRecordRepository; + + /** + * 会员留存分析 + */ + public MemberRetentionAnalysis analyzeMemberRetention(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) { + List members = memberRepository.findByTenantIdAndStoreIdAndCreatedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + Map retentionData = new HashMap<>(); + + for (Member member : members) { + int retentionDays = calculateRetentionDays(member.getCreatedAt(), LocalDate.now()); + retentionData.put(retentionDays, retentionData.getOrDefault(retentionDays, 0L) + 1); + } + + MemberRetentionAnalysis analysis = new MemberRetentionAnalysis(); + analysis.setTotalMembers(members.size()); + analysis.setRetentionData(retentionData); + + return analysis; + } + + /** + * 计算留存天数 + */ + private int calculateRetentionDays(LocalDateTime createdAt, LocalDate now) { + return (int) ChronoUnit.DAYS.between(createdAt.toLocalDate(), now); + } + + /** + * 预约转化率分析 + */ + public BookingConversionAnalysis analyzeBookingConversion(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) { + long totalBookings = bookingRecordRepository.countByTenantIdAndStoreIdAndBookedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + long totalCheckIns = checkInRecordRepository.countByTenantIdAndStoreIdAndCheckInAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + BookingConversionAnalysis analysis = new BookingConversionAnalysis(); + analysis.setTotalBookings(totalBookings); + analysis.setTotalCheckIns(totalCheckIns); + analysis.setConversionRate(totalBookings > 0 ? (double) totalCheckIns / totalBookings : 0.0); + + return analysis; + } +} +``` + +--- + +## 六、营销分析与预测模块设计 + +### 6.1 模块概述 + +营销分析与预测模块是付费订阅版的高级功能模块,负责: + +- 营销精算模型预测促销策略 +- 多维度自定义促销活动 +- 促销活动效果预测 + +### 6.2 数据模型设计 + +**营销预测表 (marketing_prediction)** + +```sql +CREATE TABLE marketing_prediction ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + prediction_no VARCHAR(32) NOT NULL, + activity_type SMALLINT NOT NULL, + parameters JSONB NOT NULL, + predicted_data JSONB NOT NULL, + accuracy DECIMAL(5,4), + model_version VARCHAR(32), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_marketing_prediction_no UNIQUE (prediction_no), + CONSTRAINT fk_marketing_prediction_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +**促销活动表 (promotion_activity)** + +```sql +CREATE TABLE promotion_activity ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + type SMALLINT NOT NULL, + description TEXT, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + target_audience JSONB NOT NULL, + discount_rule JSONB NOT NULL, + budget DECIMAL(10,2), + predicted_data JSONB, + actual_data JSONB, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_promotion_activity_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +### 6.3 核心服务设计 + +**营销精算模型服务** + +```java +@Service +public class MarketingActuarialModelService { + + @Autowired + private MarketingPredictionRepository marketingPredictionRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private BookingRecordRepository bookingRecordRepository; + + @Autowired + private CheckInRecordRepository checkInRecordRepository; + + /** + * 预测促销策略 + */ + @Transactional + public MarketingPrediction predictPromotionStrategy(PromotionPredictionRequest request) { + Map historicalData = collectHistoricalData(request.getTenantId(), request.getStoreId()); + + Map predictedData = runPredictionModel(historicalData, request.getParameters()); + + MarketingPrediction prediction = new MarketingPrediction(); + prediction.setTenantId(request.getTenantId()); + prediction.setPredictionNo(generatePredictionNo(request.getTenantId())); + prediction.setActivityType(request.getActivityType()); + prediction.setParameters(request.getParameters()); + prediction.setPredictedData(predictedData); + prediction.setAccuracy(calculateAccuracy(historicalData, predictedData)); + prediction.setModelVersion("v1.0"); + + return marketingPredictionRepository.save(prediction); + } + + /** + * 收集历史数据 + */ + private Map collectHistoricalData(Long tenantId, Long storeId) { + Map historicalData = new HashMap<>(); + + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusMonths(6); + + long totalMembers = memberRepository.countByTenantIdAndStoreIdAndCreatedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + long totalBookings = bookingRecordRepository.countByTenantIdAndStoreIdAndBookedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + long totalCheckIns = checkInRecordRepository.countByTenantIdAndStoreIdAndCheckInAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + historicalData.put("totalMembers", totalMembers); + historicalData.put("totalBookings", totalBookings); + historicalData.put("totalCheckIns", totalCheckIns); + + return historicalData; + } + + /** + * 运行预测模型 + */ + private Map runPredictionModel(Map historicalData, Map parameters) { + Map predictedData = new HashMap<>(); + + Long totalMembers = (Long) historicalData.get("totalMembers"); + Long totalBookings = (Long) historicalData.get("totalBookings"); + Long totalCheckIns = (Long) historicalData.get("totalCheckIns"); + + Double discountRate = (Double) parameters.get("discountRate"); + Integer durationDays = (Integer) parameters.get("durationDays"); + + double predictedNewMembers = totalMembers * 0.1 * (1 + discountRate * 2); + double predictedBookings = totalBookings * 1.2 * (1 + discountRate * 1.5); + double predictedCheckIns = totalCheckIns * 1.15 * (1 + discountRate * 1.3); + + predictedData.put("predictedNewMembers", Math.round(predictedNewMembers)); + predictedData.put("predictedBookings", Math.round(predictedBookings)); + predictedData.put("predictedCheckIns", Math.round(predictedCheckIns)); + predictedData.put("predictedRevenue", predictedBookings * 100 * (1 - discountRate)); + + return predictedData; + } + + /** + * 计算准确率 + */ + private BigDecimal calculateAccuracy(Map historicalData, Map predictedData) { + return BigDecimal.valueOf(0.85); + } + + /** + * 生成预测号 + */ + private String generatePredictionNo(Long tenantId) { + String prefix = "P" + tenantId; + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = String.valueOf(new Random().nextInt(1000)); + return prefix + timestamp.substring(timestamp.length() - 8) + random; + } +} +``` + +**促销活动服务** + +```java +@Service +public class PromotionActivityService { + + @Autowired + private PromotionActivityRepository promotionActivityRepository; + + @Autowired + private MarketingActuarialModelService marketingActuarialModelService; + + /** + * 创建促销活动 + */ + @Transactional + public PromotionActivity createActivity(PromotionActivityCreateRequest request) { + PromotionActivity activity = new PromotionActivity(); + activity.setTenantId(request.getTenantId()); + activity.setName(request.getName()); + activity.setType(request.getType()); + activity.setDescription(request.getDescription()); + activity.setStartDate(request.getStartDate()); + activity.setEndDate(request.getEndDate()); + activity.setTargetAudience(request.getTargetAudience()); + activity.setDiscountRule(request.getDiscountRule()); + activity.setBudget(request.getBudget()); + activity.setStatus(1); + + activity = promotionActivityRepository.save(activity); + + if (request.isPredictEffect()) { + predictActivityEffect(activity); + } + + return activity; + } + + /** + * 预测活动效果 + */ + private void predictActivityEffect(PromotionActivity activity) { + PromotionPredictionRequest predictionRequest = new PromotionPredictionRequest(); + predictionRequest.setTenantId(activity.getTenantId()); + predictionRequest.setActivityType(activity.getType()); + predictionRequest.setParameters(activity.getDiscountRule()); + + MarketingPrediction prediction = marketingActuarialModelService.predictPromotionStrategy(predictionRequest); + + activity.setPredictedData(prediction.getPredictedData()); + promotionActivityRepository.save(activity); + } +} +``` + +--- + +## 七、缓存策略 + +### 7.1 缓存设计 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 缓存策略 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 本地缓存 (Caffeine) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员信息缓存 (TTL: 30分钟) │ │ +│ │ • 会员卡缓存 (TTL: 30分钟) │ │ +│ │ • 课程信息缓存 (TTL: 1小时) │ │ +│ │ • 配置信息缓存 (TTL: 1小时) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 分布式缓存 (Redis) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 验证码缓存 (TTL: 5分钟) │ │ +│ │ • 令牌缓存 (TTL: 24小时) │ │ +│ │ • 限流计数器 (TTL: 1分钟) │ │ +│ │ • 预测结果缓存 (TTL: 1小时) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 八、API设计 + +### 8.1 订阅与配置模块API + +#### 8.1.1 订阅模块 + +``` +POST /api/v1/subscriptions/subscribe + +Request: +{ + "tenantId": 1, + "moduleCode": "private_class", + "billingCycle": 1, + "amount": 299.00, + "discountAmount": 0.00, + "actualAmount": 299.00 +} + +Response: +{ + "code": 200, + "message": "订阅成功", + "data": { + "id": 1, + "subscriptionNo": "S10000000000000001", + "moduleCode": "private_class", + "moduleName": "私教管理模块", + "startDate": "2026-03-04", + "endDate": "2026-04-03", + "status": 1 + } +} +``` + +#### 8.1.2 配置查询 + +``` +GET /api/v1/configs/module?tenantId=1&storeId=1&moduleCode=private_class + +Response: +{ + "code": 200, + "message": "查询成功", + "data": { + "moduleCode": "private_class", + "enabled": true, + "configData": { + "maxBookingDays": 7, + "cancelHours": 24 + } + } +} +``` + +### 8.2 营销分析与预测模块API + +#### 8.2.1 预测促销策略 + +``` +POST /api/v1/marketing/predict + +Request: +{ + "tenantId": 1, + "storeId": 1, + "activityType": 1, + "parameters": { + "discountRate": 0.2, + "durationDays": 30, + "targetAudience": "new_members" + } +} + +Response: +{ + "code": 200, + "message": "预测成功", + "data": { + "id": 1, + "predictionNo": "P10000000000000001", + "activityType": 1, + "predictedData": { + "predictedNewMembers": 120, + "predictedBookings": 600, + "predictedCheckIns": 920, + "predictedRevenue": 48000.00 + }, + "accuracy": 0.85 + } +} +``` + +--- + +## 九、测试用例 + +### 9.1 订阅与配置模块测试用例 + +#### 9.1.1 订阅模块测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常订阅 | 租户ID、模块代码、计费周期 | 订阅成功 | +| 重复订阅 | 已订阅的模块 | 提示该模块已订阅 | +| 支付失败 | 支付失败 | 提示支付失败 | + +#### 9.1.2 配置查询测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 查询租户配置 | 租户ID、模块代码 | 返回租户配置 | +| 查询门店配置 | 租户ID、门店ID、模块代码 | 返回合并后的配置 | +| 查询默认配置 | 不存在的配置 | 返回默认配置 | + +### 9.2 营销分析与预测模块测试用例 + +#### 9.2.1 预测促销策略测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常预测 | 租户ID、活动类型、参数 | 预测成功 | +| 历史数据不足 | 新租户 | 提示历史数据不足 | +| 参数无效 | 无效的参数 | 提示参数无效 | + +--- + +## 十、部署与运维 + +### 10.1 部署架构 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 部署架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 负载均衡 (Nginx) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 应用服务器 (Kubernetes) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Pod 1 • Pod 2 • Pod 3 • Pod 4 • Pod 5 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 数据库 (PostgreSQL) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 主库 • 从库 • 从库 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 缓存 (Redis) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 主节点 • 从节点 • 从节点 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 搜索引擎 (Elasticsearch) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 节点1 • 节点2 • 节点3 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 10.2 监控指标 + +| 指标类型 | 指标名称 | 阈值 | +|---------|---------|------| +| 系统指标 | CPU使用率 | ≤ 80% | +| 系统指标 | 内存使用率 | ≤ 80% | +| 系统指标 | 磁盘使用率 | ≤ 80% | +| 应用指标 | API响应时间 | ≤ 500ms | +| 应用指标 | 错误率 | ≤ 1% | +| 应用指标 | 并发数 | ≤ 500 | +| 业务指标 | 订阅成功率 | ≥ 98% | +| 业务指标 | 预测准确率 | ≥ 75% | + +--- + +## 十一、附录 + +### 11.1 术语定义 + +| 术语 | 定义 | +|------|------| +| 订阅模块 | 按需订阅的增值功能模块 | +| 配置继承 | 门店配置继承租户配置的机制 | +| 私教管理 | 私教课程管理、私教预约、私教签到等功能 | +| 营销活动 | 吸引新会员和提升会员活跃度的活动 | +| 营销精算模型 | 基于历史数据预测促销策略的模型 | +| 促销活动效果预测 | 基于历史数据预测促销活动效果 | + +### 11.2 参考文档 + +- 《健身房管理系统付费订阅版产品设计文档》 GYM-PRD-SUBSCRIPTION-001 +- 《健身房管理系统付费订阅版业务概要设计文档》 GYM-HLD-SUBSCRIPTION-001 +- 《健身房管理系统详细设计文档》 GYM-LLD-000 +- 《订阅与配置模块详细设计文档》 GYM-LLD-004 +- Spring Boot 3 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 diff --git a/docs/design/LLD-基础版系统详细设计.md b/docs/design/LLD-基础版系统详细设计.md new file mode 100644 index 0000000..4040829 --- /dev/null +++ b/docs/design/LLD-基础版系统详细设计.md @@ -0,0 +1,1346 @@ +# 健身房管理系统基础版详细设计文档(LLD) + +> 文档编号: GYM-LLD-BASIC-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-04 | 张翔 | 创建基础版详细设计 | + +--- + +## 参考文档 + +- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001 +- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001 +- 《健身房管理系统详细设计文档》 GYM-LLD-000 +- Spring Boot 3 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 + +--- + +## 一、系统架构设计 + +### 1.1 总体架构 + +采用分层架构 + 模块化设计: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 基础版总体架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 客户端层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员小程序 (uniapp+Vue3) │ │ +│ │ • 教练端App (uniapp+Vue3) │ │ +│ │ • 管理后台PC (Vue3+Vite) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ API Gateway 统一网关 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 路由转发 • 认证鉴权 • 限流熔断 • 日志追踪 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 业务层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员服务 (Member Service) │ │ +│ │ • 预约服务 (Booking Service) │ │ +│ │ • 签到服务 (CheckIn Service) │ │ +│ │ • 数据服务 (Data Service) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 公共服务层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 认证服务 • 消息服务 • 文件服务 • 缓存服务 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 基础设施层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • PostgreSQL • R2DBC • Caffeine • Redis(可选) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 外部服务层 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 微信开放平台 • 短信服务 • 支付服务 • OSS存储 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 技术架构 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 技术架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 前端技术栈 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • uniapp (跨平台小程序) • Vue3 (前端框架) │ │ +│ │ • Vite (构建工具) • TypeScript (类型安全) │ │ +│ │ • Pinia (状态管理) • Element Plus (UI组件库) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 后端技术栈 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Spring Boot 3.2 (应用框架) • Spring Security (安全框架) │ │ +│ │ • R2DBC (响应式数据库) • Spring WebFlux (响应式Web) │ │ +│ │ • Caffeine (本地缓存) • Redis (分布式缓存) │ │ +│ │ • PostgreSQL (关系型数据库) • MyBatis (ORM框架) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 部署架构 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Docker (容器化) • Kubernetes (容器编排) │ │ +│ │ • Nginx (反向代理) • ELK (日志收集) │ │ +│ │ • Prometheus (监控) • Grafana (可视化) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、模块设计 + +### 2.1 会员模块 + +#### 2.1.1 模块概述 + +会员模块是基础版的核心基础模块,负责管理会员全生命周期,包括: + +- 会员注册与信息管理 +- 会员卡购买与管理 +- 会员权益(时长/次数/储值/等级)管理 + +#### 2.1.2 数据模型设计 + +**会员表 (member)** + +```sql +CREATE TABLE member ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + member_no VARCHAR(32) NOT NULL, + name VARCHAR(64), + phone VARCHAR(64) NOT NULL, + phone_mask VARCHAR(20), + avatar VARCHAR(512), + gender SMALLINT, + birthday DATE, + height INT, + weight DECIMAL(5,2), + fitness_goal VARCHAR(64), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_member_tenant_no UNIQUE (tenant_id, member_no), + CONSTRAINT uk_member_phone UNIQUE (phone), + CONSTRAINT fk_member_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_member_store FOREIGN KEY (store_id) REFERENCES store(id) +); +``` + +**会员卡表 (member_card)** + +```sql +CREATE TABLE member_card ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + card_no VARCHAR(32) NOT NULL, + card_type SMALLINT NOT NULL, + card_name VARCHAR(64) NOT NULL, + total_amount DECIMAL(10,2), + balance DECIMAL(10,2), + total_count INT, + balance_count INT, + valid_days INT, + valid_from DATE, + valid_to DATE, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_member_card_no UNIQUE (card_no), + CONSTRAINT fk_member_card_member FOREIGN KEY (member_id) REFERENCES member(id) +); +``` + +#### 2.1.3 核心服务设计 + +**会员注册服务** + +```java +@Service +public class MemberRegistrationService { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private SmsService smsService; + + @Autowired + private MemberCardService memberCardService; + + /** + * 会员注册 + */ + @Transactional + public Member register(MemberRegistrationRequest request) { + validatePhone(request.getPhone()); + validateSmsCode(request.getPhone(), request.getSmsCode()); + + Member member = new Member(); + member.setTenantId(request.getTenantId()); + member.setStoreId(request.getStoreId()); + member.setMemberNo(generateMemberNo(request.getTenantId())); + member.setName(request.getName()); + member.setPhone(encryptPhone(request.getPhone())); + member.setPhoneMask(maskPhone(request.getPhone())); + member.setGender(request.getGender()); + member.setBirthday(request.getBirthday()); + member.setHeight(request.getHeight()); + member.setWeight(request.getWeight()); + member.setFitnessGoal(request.getFitnessGoal()); + member.setStatus(1); + + return memberRepository.save(member); + } + + /** + * 验证手机号 + */ + private void validatePhone(String phone) { + if (!memberRepository.findByPhone(phone).isEmpty()) { + throw new BusinessException("手机号已存在"); + } + } + + /** + * 验证短信验证码 + */ + private void validateSmsCode(String phone, String smsCode) { + if (!smsService.verifySmsCode(phone, smsCode)) { + throw new BusinessException("验证码错误"); + } + } + + /** + * 生成会员号 + */ + private String generateMemberNo(Long tenantId) { + String prefix = "M" + tenantId; + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = String.valueOf(new Random().nextInt(1000)); + return prefix + timestamp.substring(timestamp.length() - 8) + random; + } + + /** + * 加密手机号 + */ + private String encryptPhone(String phone) { + return AESUtil.encrypt(phone); + } + + /** + * 脱敏手机号 + */ + private String maskPhone(String phone) { + return phone.substring(0, 3) + "****" + phone.substring(7); + } +} +``` + +**会员卡购买服务** + +```java +@Service +public class MemberCardPurchaseService { + + @Autowired + private MemberCardRepository memberCardRepository; + + @Autowired + private PaymentService paymentService; + + @Autowired + private BenefitService benefitService; + + /** + * 购买会员卡 + */ + @Transactional + public MemberCard purchase(MemberCardPurchaseRequest request) { + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new BusinessException("会员不存在")); + + Payment payment = paymentService.createPayment(request); + + MemberCard memberCard = new MemberCard(); + memberCard.setTenantId(member.getTenantId()); + memberCard.setStoreId(member.getStoreId()); + memberCard.setMemberId(member.getId()); + memberCard.setCardNo(generateCardNo(member.getTenantId())); + memberCard.setCardType(request.getCardType()); + memberCard.setCardName(request.getCardName()); + memberCard.setTotalAmount(request.getAmount()); + memberCard.setBalance(request.getAmount()); + memberCard.setTotalCount(request.getCount()); + memberCard.setBalanceCount(request.getCount()); + memberCard.setValidDays(request.getValidDays()); + memberCard.setValidFrom(LocalDate.now()); + memberCard.setValidTo(LocalDate.now().plusDays(request.getValidDays())); + memberCard.setStatus(1); + + memberCard = memberCardRepository.save(memberCard); + + benefitService.createBenefits(memberCard); + + return memberCard; + } + + /** + * 生成卡号 + */ + private String generateCardNo(Long tenantId) { + String prefix = "C" + tenantId; + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = String.valueOf(new Random().nextInt(1000)); + return prefix + timestamp.substring(timestamp.length() - 8) + random; + } +} +``` + +--- + +### 2.2 预约模块 + +#### 2.2.1 模块概述 + +预约模块是基础版的核心业务模块,负责管理团课预约,包括: + +- 团课管理 +- 团课预约 +- 预约取消 + +#### 2.2.2 数据模型设计 + +**课程表 (course)** + +```sql +CREATE TABLE course ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + code VARCHAR(32), + type SMALLINT NOT NULL, + category VARCHAR(64), + description TEXT, + cover_image VARCHAR(512), + duration INT NOT NULL, + capacity INT DEFAULT 20, + min_capacity INT DEFAULT 1, + difficulty SMALLINT DEFAULT 1, + calories INT, + equipment VARCHAR(256), + benefits JSONB, + price DECIMAL(10,2), + price_type SMALLINT DEFAULT 1, + price_value DECIMAL(10,2), + advance_days INT DEFAULT 7, + cancel_hours INT DEFAULT 2, + cancel_penalty DECIMAL(3,2) DEFAULT 0.00, + status SMALLINT DEFAULT 1, + sort_order INT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_course_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +**课程时段表 (course_slot)** + +```sql +CREATE TABLE course_slot ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + course_id BIGINT NOT NULL, + coach_id BIGINT, + slot_date DATE NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + capacity INT DEFAULT 20, + booked_count INT DEFAULT 0, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_course_slot_course FOREIGN KEY (course_id) REFERENCES course(id), + CONSTRAINT fk_course_slot_coach FOREIGN KEY (coach_id) REFERENCES coach(id) +); +``` + +**预约记录表 (booking_record)** + +```sql +CREATE TABLE booking_record ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + course_id BIGINT NOT NULL, + slot_id BIGINT NOT NULL, + booking_no VARCHAR(32) NOT NULL, + status SMALLINT DEFAULT 1, + booked_at TIMESTAMP DEFAULT NOW(), + cancelled_at TIMESTAMP, + cancel_reason VARCHAR(256), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_booking_no UNIQUE (booking_no), + CONSTRAINT fk_booking_member FOREIGN KEY (member_id) REFERENCES member(id), + CONSTRAINT fk_booking_slot FOREIGN KEY (slot_id) REFERENCES course_slot(id) +); +``` + +#### 2.2.3 核心服务设计 + +**团课预约服务** + +```java +@Service +public class CourseBookingService { + + @Autowired + private BookingRecordRepository bookingRecordRepository; + + @Autowired + private CourseSlotRepository courseSlotRepository; + + @Autowired + private BenefitService benefitService; + + @Autowired + private MessageService messageService; + + /** + * 预约团课 + */ + @Transactional + public BookingRecord book(CourseBookingRequest request) { + validateBookingTime(request.getSlotId()); + validateCapacity(request.getSlotId()); + validateMemberBenefit(request.getMemberId(), request.getSlotId()); + + CourseSlot slot = courseSlotRepository.findById(request.getSlotId()) + .orElseThrow(() -> new BusinessException("课程时段不存在")); + + BookingRecord record = new BookingRecord(); + record.setTenantId(slot.getTenantId()); + record.setStoreId(slot.getStoreId()); + record.setMemberId(request.getMemberId()); + record.setCourseId(slot.getCourseId()); + record.setSlotId(slot.getId()); + record.setBookingNo(generateBookingNo(slot.getTenantId())); + record.setStatus(1); + + record = bookingRecordRepository.save(record); + + slot.setBookedCount(slot.getBookedCount() + 1); + courseSlotRepository.save(slot); + + benefitService.deductBenefit(request.getMemberId(), slot.getCourseId()); + + messageService.sendBookingNotification(record); + + return record; + } + + /** + * 验证预约时间 + */ + private void validateBookingTime(Long slotId) { + CourseSlot slot = courseSlotRepository.findById(slotId) + .orElseThrow(() -> new BusinessException("课程时段不存在")); + + LocalDateTime slotDateTime = LocalDateTime.of(slot.getSlotDate(), slot.getStartTime()); + if (slotDateTime.isBefore(LocalDateTime.now().plusMinutes(30))) { + throw new BusinessException("预约时间过短"); + } + } + + /** + * 验证容量 + */ + private void validateCapacity(Long slotId) { + CourseSlot slot = courseSlotRepository.findById(slotId) + .orElseThrow(() -> new BusinessException("课程时段不存在")); + + if (slot.getBookedCount() >= slot.getCapacity()) { + throw new BusinessException("课程已满"); + } + } + + /** + * 验证会员权益 + */ + private void validateMemberBenefit(Long memberId, Long slotId) { + if (!benefitService.hasBenefit(memberId, slotId)) { + throw new BusinessException("会员权益不足"); + } + } + + /** + * 生成预约号 + */ + private String generateBookingNo(Long tenantId) { + String prefix = "B" + tenantId; + String timestamp = String.valueOf(System.currentTimeMillis()); + String random = String.valueOf(new Random().nextInt(1000)); + return prefix + timestamp.substring(timestamp.length() - 8) + random; + } +} +``` + +--- + +### 2.3 签到模块 + +#### 2.3.1 模块概述 + +签到模块是基础版的核心业务模块,负责管理会员的入场签到,支持: + +- 二维码签到 +- 签到记录管理 + +#### 2.3.2 数据模型设计 + +**签到记录表 (checkin_record)** + +```sql +CREATE TABLE checkin_record ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + booking_id BIGINT, + type SMALLINT NOT NULL, + method SMALLINT NOT NULL, + status SMALLINT DEFAULT 1, + checkin_at TIMESTAMP NOT NULL, + checkin_date DATE NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_checkin_member FOREIGN KEY (member_id) REFERENCES member(id), + CONSTRAINT fk_checkin_booking FOREIGN KEY (booking_id) REFERENCES booking_record(id) +); +``` + +#### 2.3.3 核心服务设计 + +**扫码签到服务** + +```java +@Service +public class QRCodeCheckInService { + + @Autowired + private CheckInRecordRepository checkInRecordRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private MemberCardRepository memberCardRepository; + + /** + * 扫码签到 + */ + @Transactional + public CheckInRecord checkIn(QRCodeCheckInRequest request) { + Member member = memberRepository.findByQrCode(request.getQrCode()) + .orElseThrow(() -> new BusinessException("会员不存在")); + + validateMemberCard(member.getId()); + + CheckInRecord record = new CheckInRecord(); + record.setTenantId(member.getTenantId()); + record.setStoreId(member.getStoreId()); + record.setMemberId(member.getId()); + record.setType(1); + record.setMethod(1); + record.setStatus(1); + record.setCheckInAt(LocalDateTime.now()); + record.setCheckInDate(LocalDate.now()); + + return checkInRecordRepository.save(record); + } + + /** + * 验证会员卡 + */ + private void validateMemberCard(Long memberId) { + List cards = memberCardRepository.findByMemberId(memberId); + if (cards.isEmpty()) { + throw new BusinessException("会员卡不存在"); + } + + boolean hasValidCard = cards.stream() + .anyMatch(card -> card.getStatus() == 1 && + (card.getValidTo() == null || card.getValidTo().isAfter(LocalDate.now()))); + + if (!hasValidCard) { + throw new BusinessException("会员卡无效"); + } + } +} +``` + +--- + +### 2.4 数据统计模块 + +#### 2.4.1 模块概述 + +数据统计模块是基础版的核心业务模块,负责提供基础数据统计,包括: + +- 会员数据统计 +- 预约数据统计 +- 签到数据统计 + +#### 2.4.2 核心服务设计 + +**数据统计服务** + +```java +@Service +public class DataStatisticsService { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private BookingRecordRepository bookingRecordRepository; + + @Autowired + private CheckInRecordRepository checkInRecordRepository; + + /** + * 获取会员数据统计 + */ + public MemberStatistics getMemberStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) { + long totalMembers = memberRepository.countByTenantIdAndStoreIdAndCreatedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + long activeMembers = memberRepository.countActiveMembers(tenantId, storeId, startDate, endDate); + + MemberStatistics statistics = new MemberStatistics(); + statistics.setTotalMembers(totalMembers); + statistics.setActiveMembers(activeMembers); + statistics.setNewMembers(totalMembers); + + return statistics; + } + + /** + * 获取预约数据统计 + */ + public BookingStatistics getBookingStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) { + long totalBookings = bookingRecordRepository.countByTenantIdAndStoreIdAndBookedAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + long cancelledBookings = bookingRecordRepository.countCancelledBookings(tenantId, storeId, startDate, endDate); + + BookingStatistics statistics = new BookingStatistics(); + statistics.setTotalBookings(totalBookings); + statistics.setCancelledBookings(cancelledBookings); + statistics.setSuccessBookings(totalBookings - cancelledBookings); + + return statistics; + } + + /** + * 获取签到数据统计 + */ + public CheckInStatistics getCheckInStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) { + long totalCheckIns = checkInRecordRepository.countByTenantIdAndStoreIdAndCheckInAtBetween( + tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); + + CheckInStatistics statistics = new CheckInStatistics(); + statistics.setTotalCheckIns(totalCheckIns); + + return statistics; + } +} +``` + +--- + +### 2.5 系统管理模块 + +#### 2.5.1 模块概述 + +系统管理模块是基础版的核心基础模块,负责管理系统用户和权限,包括: + +- 用户管理 +- 角色权限管理 + +#### 2.5.2 数据模型设计 + +**用户表 (user)** + +```sql +CREATE TABLE user ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT, + username VARCHAR(64) NOT NULL, + password VARCHAR(256) NOT NULL, + name VARCHAR(64) NOT NULL, + phone VARCHAR(64), + email VARCHAR(128), + avatar VARCHAR(512), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_user_username UNIQUE (username), + CONSTRAINT fk_user_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_user_store FOREIGN KEY (store_id) REFERENCES store(id) +); +``` + +**角色表 (role)** + +```sql +CREATE TABLE role ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(64) NOT NULL, + code VARCHAR(32) NOT NULL, + description VARCHAR(256), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_role_code UNIQUE (code), + CONSTRAINT fk_role_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); +``` + +**用户角色关联表 (user_role)** + +```sql +CREATE TABLE user_role ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + + CONSTRAINT uk_user_role UNIQUE (user_id, role_id), + CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES user(id), + CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES role(id) +); +``` + +#### 2.5.3 核心服务设计 + +**用户管理服务** + +```java +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserRoleRepository userRoleRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + /** + * 创建用户 + */ + @Transactional + public User createUser(UserCreateRequest request) { + validateUsername(request.getUsername()); + + User user = new User(); + user.setTenantId(request.getTenantId()); + user.setStoreId(request.getStoreId()); + user.setUsername(request.getUsername()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setName(request.getName()); + user.setPhone(request.getPhone()); + user.setEmail(request.getEmail()); + user.setStatus(1); + + user = userRepository.save(user); + + assignRoles(user.getId(), request.getRoleIds()); + + return user; + } + + /** + * 验证用户名 + */ + private void validateUsername(String username) { + if (userRepository.findByUsername(username).isPresent()) { + throw new BusinessException("用户名已存在"); + } + } + + /** + * 分配角色 + */ + private void assignRoles(Long userId, List roleIds) { + if (roleIds == null || roleIds.isEmpty()) { + return; + } + + List userRoles = roleIds.stream() + .map(roleId -> { + UserRole userRole = new UserRole(); + userRole.setUserId(userId); + userRole.setRoleId(roleId); + return userRole; + }) + .collect(Collectors.toList()); + + userRoleRepository.saveAll(userRoles); + } +} +``` + +--- + +## 三、API设计 + +### 3.1 会员模块API + +#### 3.1.1 会员注册 + +``` +POST /api/v1/members/register + +Request: +{ + "tenantId": 1, + "storeId": 1, + "phone": "13800138000", + "smsCode": "123456", + "name": "张三", + "gender": 1, + "birthday": "1990-01-01", + "height": 175, + "weight": 70.5, + "fitnessGoal": "减脂" +} + +Response: +{ + "code": 200, + "message": "注册成功", + "data": { + "id": 1, + "memberNo": "M10000000000000001", + "name": "张三", + "phoneMask": "138****8000", + "status": 1 + } +} +``` + +#### 3.1.2 购买会员卡 + +``` +POST /api/v1/member-cards/purchase + +Request: +{ + "memberId": 1, + "cardType": 1, + "cardName": "月卡", + "amount": 299.00, + "count": 30, + "validDays": 30 +} + +Response: +{ + "code": 200, + "message": "购买成功", + "data": { + "id": 1, + "cardNo": "C10000000000000001", + "cardName": "月卡", + "balance": 299.00, + "balanceCount": 30, + "validFrom": "2026-03-04", + "validTo": "2026-04-03", + "status": 1 + } +} +``` + +### 3.2 预约模块API + +#### 3.2.1 预约团课 + +``` +POST /api/v1/bookings/course + +Request: +{ + "memberId": 1, + "slotId": 1 +} + +Response: +{ + "code": 200, + "message": "预约成功", + "data": { + "id": 1, + "bookingNo": "B10000000000000001", + "courseName": "瑜伽", + "slotDate": "2026-03-05", + "startTime": "10:00:00", + "endTime": "11:00:00", + "status": 1 + } +} +``` + +### 3.3 签到模块API + +#### 3.3.1 扫码签到 + +``` +POST /api/v1/checkins/qrcode + +Request: +{ + "qrCode": "M10000000000000001" +} + +Response: +{ + "code": 200, + "message": "签到成功", + "data": { + "id": 1, + "memberName": "张三", + "checkInAt": "2026-03-04T10:00:00", + "status": 1 + } +} +``` + +### 3.4 数据统计模块API + +#### 3.4.1 获取数据统计 + +``` +GET /api/v1/statistics/overview?tenantId=1&storeId=1&startDate=2026-03-01&endDate=2026-03-31 + +Response: +{ + "code": 200, + "message": "查询成功", + "data": { + "memberStatistics": { + "totalMembers": 100, + "activeMembers": 80, + "newMembers": 20 + }, + "bookingStatistics": { + "totalBookings": 500, + "cancelledBookings": 50, + "successBookings": 450 + }, + "checkInStatistics": { + "totalCheckIns": 800 + } + } +} +``` + +--- + +## 四、缓存策略 + +### 4.1 缓存设计 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 缓存策略 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 本地缓存 (Caffeine) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员信息缓存 (TTL: 30分钟) │ │ +│ │ • 会员卡缓存 (TTL: 30分钟) │ │ +│ │ • 课程信息缓存 (TTL: 1小时) │ │ +│ │ • 课程时段缓存 (TTL: 30分钟) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 分布式缓存 (Redis) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 验证码缓存 (TTL: 5分钟) │ │ +│ │ • 令牌缓存 (TTL: 24小时) │ │ +│ │ • 限流计数器 (TTL: 1分钟) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 缓存实现 + +**会员缓存服务** + +```java +@Service +public class MemberCacheService { + + @Autowired + private CacheManager cacheManager; + + private static final String MEMBER_CACHE = "member"; + private static final String MEMBER_CARD_CACHE = "memberCard"; + + /** + * 获取会员缓存 + */ + public Optional getMember(Long memberId) { + Cache cache = cacheManager.getCache(MEMBER_CACHE); + if (cache != null) { + return Optional.ofNullable(cache.get(memberId, Member.class)); + } + return Optional.empty(); + } + + /** + * 设置会员缓存 + */ + public void setMember(Member member) { + Cache cache = cacheManager.getCache(MEMBER_CACHE); + if (cache != null) { + cache.put(member.getId(), member); + } + } + + /** + * 删除会员缓存 + */ + public void evictMember(Long memberId) { + Cache cache = cacheManager.getCache(MEMBER_CACHE); + if (cache != null) { + cache.evict(memberId); + } + } + + /** + * 获取会员卡缓存 + */ + public Optional getMemberCard(Long cardId) { + Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE); + if (cache != null) { + return Optional.ofNullable(cache.get(cardId, MemberCard.class)); + } + return Optional.empty(); + } + + /** + * 设置会员卡缓存 + */ + public void setMemberCard(MemberCard card) { + Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE); + if (cache != null) { + cache.put(card.getId(), card); + } + } + + /** + * 删除会员卡缓存 + */ + public void evictMemberCard(Long cardId) { + Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE); + if (cache != null) { + cache.evict(cardId); + } + } +} +``` + +--- + +## 五、异常处理 + +### 5.1 异常分类 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 异常分类 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 业务异常 (BusinessException) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 会员不存在 • 会员卡无效 • 预约失败 • 签到失败 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 参数异常 (ValidationException) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 参数为空 • 参数格式错误 • 参数超出范围 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 权限异常 (PermissionException) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 无权限 • 权限不足 • 令牌过期 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 系统异常 (SystemException) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 数据库异常 • 网络异常 • 服务异常 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 5.2 异常处理实现 + +**全局异常处理器** + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 业务异常 + */ + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException e) { + ErrorResponse response = new ErrorResponse(); + response.setCode(e.getCode()); + response.setMessage(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + + /** + * 参数异常 + */ + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidationException(ValidationException e) { + ErrorResponse response = new ErrorResponse(); + response.setCode(400); + response.setMessage(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + + /** + * 权限异常 + */ + @ExceptionHandler(PermissionException.class) + public ResponseEntity handlePermissionException(PermissionException e) { + ErrorResponse response = new ErrorResponse(); + response.setCode(403); + response.setMessage(e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response); + } + + /** + * 系统异常 + */ + @ExceptionHandler(SystemException.class) + public ResponseEntity handleSystemException(SystemException e) { + ErrorResponse response = new ErrorResponse(); + response.setCode(500); + response.setMessage("系统异常"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } +} +``` + +--- + +## 六、测试用例 + +### 6.1 会员模块测试用例 + +#### 6.1.1 会员注册测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常注册 | 手机号、验证码、姓名 | 注册成功 | +| 手机号已存在 | 已存在的手机号 | 提示手机号已存在 | +| 验证码错误 | 错误的验证码 | 提示验证码错误 | +| 验证码过期 | 过期的验证码 | 提示验证码过期 | + +#### 6.1.2 购买会员卡测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常购买 | 会员ID、卡类型、金额 | 购买成功 | +| 会员不存在 | 不存在的会员ID | 提示会员不存在 | +| 支付失败 | 支付失败 | 提示支付失败 | + +### 6.2 预约模块测试用例 + +#### 6.2.1 预约团课测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常预约 | 会员ID、课程时段ID | 预约成功 | +| 预约时间过短 | 课程开始前30分钟内 | 提示预约时间过短 | +| 课程已满 | 已满的课程时段 | 提示课程已满 | +| 权益不足 | 权益不足的会员 | 提示权益不足 | + +### 6.3 签到模块测试用例 + +#### 6.3.1 扫码签到测试 + +| 测试用例 | 输入 | 预期输出 | +|---------|------|---------| +| 正常签到 | 有效的二维码 | 签到成功 | +| 会员不存在 | 无效的二维码 | 提示会员不存在 | +| 会员卡无效 | 会员卡无效 | 提示会员卡无效 | + +--- + +## 七、部署与运维 + +### 7.1 部署架构 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 部署架构 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 负载均衡 (Nginx) │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 应用服务器 (Kubernetes) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Pod 1 • Pod 2 • Pod 3 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 数据库 (PostgreSQL) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 主库 • 从库 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 缓存 (Redis) │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • 主节点 • 从节点 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 7.2 监控指标 + +| 指标类型 | 指标名称 | 阈值 | +|---------|---------|------| +| 系统指标 | CPU使用率 | ≤ 80% | +| 系统指标 | 内存使用率 | ≤ 80% | +| 系统指标 | 磁盘使用率 | ≤ 80% | +| 应用指标 | API响应时间 | ≤ 500ms | +| 应用指标 | 错误率 | ≤ 1% | +| 应用指标 | 并发数 | ≤ 100 | + +--- + +## 八、附录 + +### 8.1 术语定义 + +| 术语 | 定义 | +|------|------| +| 会员 | 在健身房注册的用户 | +| 会员卡 | 会员购买的权益卡,包括时长卡、次卡、储值卡 | +| 权益 | 会员卡包含的时长、次数、储值、等级等权益 | +| 团课 | 集体课程,由教练带领多个会员一起上课 | +| 预约 | 会员预约团课 | +| 签到 | 会员到店记录 | + +### 8.2 参考文档 + +- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001 +- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001 +- 《健身房管理系统详细设计文档》 GYM-LLD-000 +- Spring Boot 3 官方文档 +- R2DBC 规范文档 +- PostgreSQL 官方文档 diff --git a/docs/design/LLD-系统详细设计.md b/docs/design/LLD-系统详细设计.md deleted file mode 100644 index 932cdd6..0000000 --- a/docs/design/LLD-系统详细设计.md +++ /dev/null @@ -1,796 +0,0 @@ -# 健身房管理系统详细设计文档(LLD) - -> 文档编号: GYM-LLD-000 -> 版本: v1.0 -> 日期: 2026-03-04 -> 作者: 张翔 -> 状态: 初稿 - ---- - -## 文档修订历史 - -| 版本 | 日期 | 作者 | 修订内容 | -| ---- | ---------- | ---- | -------- | -| v1.0 | 2026-03-04 | 张翔 | 创建系统详细设计文档 | - ---- - -## 参考文档 - -- 《健身房管理系统产品设计文档》 GYM-PRD-001 -- 《健身房管理系统业务概要设计文档》 GYM-HLD-001 -- Spring Boot 3 官方文档 -- R2DBC 规范文档 -- PostgreSQL 官方文档 - ---- - -## 一、系统架构设计 - -### 1.1 总体架构 - -采用分层架构 + 微服务思想的模块化设计: - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 总体架构 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 客户端层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 会员小程序 (uniapp+Vue3) │ │ -│ │ • 教练端App (uniapp+Vue3) │ │ -│ │ • 管理后台PC (Vue3+Vite) │ │ -│ │ • 硬件设备 (人脸/NFC) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ API Gateway 统一网关 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 路由转发 • 认证鉴权 • 限流熔断 • 日志追踪 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 业务层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 会员服务 (Member Service) │ │ -│ │ • 预约服务 (Booking Service) │ │ -│ │ • 数据服务 (Data Service) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 公共服务层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 认证服务 • 消息服务 • 文件服务 • 缓存服务 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 基础设施层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • PostgreSQL • R2DBC • Caffeine • Redis(可选) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 外部服务层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 微信开放平台 • 短信服务 • 支付服务 • OSS存储 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 1.2 技术架构 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 技术架构 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 前端技术栈 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • uniapp (跨平台小程序) • Vue3 (前端框架) │ │ -│ │ • Vite (构建工具) • TypeScript (类型安全) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 后端技术栈 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • Spring Boot 3 (应用框架) • WebFlux (响应式编程) │ │ -│ │ • JDK 21+ (运行环境) • R2DBC (响应式数据库访问) │ │ -│ │ • Spring Security (安全框架) • Caffeine (本地缓存) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 数据库技术栈 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • PostgreSQL 15+ (主数据库) • Redis (可选缓存) │ │ -│ │ • Flyway (数据库版本管理) • R2DBC PostgreSQL Driver │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 开发工具栈 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • Maven (依赖管理) • Git (版本控制) • Docker (容器化) │ │ -│ │ • IDEA (开发IDE) • Postman (接口测试) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 1.3 部署架构 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 部署架构 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 客户端层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 微信小程序 • 教练端App • 管理后台PC • 硬件设备 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ CDN层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 静态资源加速 • 图片优化 • 视频加速 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 负载均衡层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • Nginx (反向代理) • 负载均衡策略 • SSL/TLS │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 应用服务器层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • Spring Boot应用 (多实例部署) • Docker容器化 │ │ -│ │ • 健康检查 • 自动扩缩容 • 滚动更新 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 数据库层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • PostgreSQL主从复制 • Redis集群 (可选) • 备份策略 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 监控运维层 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • 日志收集 • 性能监控 • 告警通知 • 自动化运维 │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 二、模块设计 - -### 2.1 模块划分 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ gym-manage-server 父工程 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-common 公共模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • gym-common-core (核心工具类、常量、枚举) │ │ -│ │ • gym-common-redis (Redis配置可选) │ │ -│ │ • gym-common-security (安全认证公共组件) │ │ -│ │ • gym-common-log (日志公共组件) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-api API网关模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • controller (HTTP接口) • dto (数据传输对象) │ │ -│ │ • vo (视图对象) • config (API配置) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-service 业务服务模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • gym-service-member (会员服务) │ │ -│ │ • gym-service-booking (预约服务) │ │ -│ │ • gym-service-checkin (签到服务) │ │ -│ │ • gym-service-course (课程服务) │ │ -│ │ • gym-service-coach (教练服务) │ │ -│ │ • gym-service-finance (财务服务) │ │ -│ │ • gym-service-data (数据服务) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-domain 领域模型模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • model (领域模型) • event (领域事件) • service (领域服务) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-infrastructure 基础设施模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • repository (数据仓储) • cache (缓存配置) │ │ -│ │ • external (外部服务集成) • config (基础配置) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-starter 启动模块 │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • gym-admin (管理后台启动器) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 2.2 模块职责 - -| 模块 | 职责 | 依赖 | -| ------------------- | ---------------------------------- | ------------------------------ | -| gym-common-core | 提供通用工具类、常量定义、异常处理 | 无 | -| gym-common-security | 提供JWT认证、权限校验 | gym-common-core | -| gym-common-redis | 提供Redis缓存支持(可选) | gym-common-core | -| gym-common-log | 提供统一日志记录 | gym-common-core | -| gym-api | 提供HTTP接口、路由转发 | gym-service-* | -| gym-service-member | 会员管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-booking | 预约管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-checkin | 签到管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-course | 课程管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-coach | 教练管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-finance | 财务管理业务逻辑 | gym-domain, gym-infrastructure | -| gym-service-data | 数据分析业务逻辑 | gym-domain, gym-infrastructure | -| gym-domain | 领域模型、领域事件、领域服务 | 无 | -| gym-infrastructure | 数据仓储、缓存、外部服务集成 | gym-domain | -| gym-starter | 应用启动器 | 所有业务模块 | - -### 2.3 模块交互 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 模块交互 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────┐ │ -│ │ 客户端 │ │ -│ └─────┬────┘ │ -│ │ HTTP/HTTPS │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ gym-api (API网关) │ │ -│ └─────────────┬───────────────────────────────────────────────┘ │ -│ │ │ -│ ┌─────────┼─────────┐ │ -│ ▼ ▼ ▼ │ -│ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ │member │ │booking │ │checkin │ │ -│ │service │ │service │ │service │ │ -│ └───┬────┘ └───┬────┘ └───┬────┘ │ -│ │ │ │ │ -│ └───────────┼───────────┘ │ -│ ▼ │ -│ ┌───────────────┐ │ -│ │ gym-domain │ │ -│ └───────┬───────┘ │ -│ │ │ -│ ▼ │ -│ ┌───────────────┐ │ -│ │ gym-infra │ │ -│ └───────┬───────┘ │ -│ │ │ -│ ┌───────────┼───────────┐ │ -│ ▼ ▼ ▼ │ -│ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ │ PG │ │Redis │ │External│ │ -│ └────────┘ └────────┘ └────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 三、接口设计 - -### 3.1 接口规范 - -#### 3.1.1 RESTful API设计原则 - -- 使用HTTP标准方法:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除) -- 使用HTTP状态码表示请求结果:200(成功)、400(请求错误)、401(未认证)、403(无权限)、404(资源不存在)、500(服务器错误) -- 使用JSON格式进行数据交换 -- 使用统一的响应结构 - -#### 3.1.2 统一响应结构 - -```json -{ - "code": 200, - "message": "success", - "data": {}, - "timestamp": 1234567890 -} -``` - -#### 3.1.3 错误响应结构 - -```json -{ - "code": 400, - "message": "参数错误", - "data": null, - "timestamp": 1234567890 -} -``` - -#### 3.1.4 分页响应结构 - -```json -{ - "code": 200, - "message": "success", - "data": { - "list": [], - "total": 100, - "page": 1, - "pageSize": 10 - }, - "timestamp": 1234567890 -} -``` - -### 3.2 接口分组 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 接口分组 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 认证接口 /v1/auth │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • POST /login (登录) • POST /logout (登出) │ │ -│ │ • POST /refresh (刷新Token) • POST /wechat-login (微信登录) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 会员接口 /v1/members │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET / (会员列表) • GET /{id} (会员详情) │ │ -│ │ • POST / (创建会员) • PUT /{id} (更新会员) │ │ -│ │ • GET /{id}/cards (会员卡列表) • GET /{id}/benefits (权益列表)│ │ -│ │ • GET /{id}/bookings (预约记录) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 课程接口 /v1/courses │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET / (课程列表) • GET /{id} (课程详情) │ │ -│ │ • POST / (创建课程) • PUT /{id} (更新课程) │ │ -│ │ • GET /{id}/slots (可预约时段) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 预约接口 /v1/bookings │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET / (预约列表) • GET /{id} (预约详情) │ │ -│ │ • POST / (创建预约) • POST /{id}/cancel (取消预约) │ │ -│ │ • GET /my (我的预约) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 签到接口 /v1/checkins │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET / (签到列表) • POST /scan (扫码签到) │ │ -│ │ • POST /manual (手动签到) • GET /my (我的签到) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 教练接口 /v1/coaches │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET / (教练列表) • GET /{id} (教练详情) │ │ -│ │ • GET /{id}/schedule (教练排班) • GET /{id}/slots (可预约时段)│ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ 数据看板 /v1/dashboard │ │ -│ ├─────────────────────────────────────────────────────────────────┤ │ -│ │ • GET /overview (今日概览) • GET /trends (趋势数据) │ │ -│ │ • GET /rankings (排行数据) │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 3.3 接口版本管理 - -#### 3.3.1 版本策略 - -- 采用URL路径版本控制:/v1/、/v2/ -- 主版本号变更表示不兼容的API变更 -- 次版本号变更表示向后兼容的功能新增 -- 修订版本号变更表示向后兼容的问题修复 - -#### 3.3.2 版本兼容性 - -- 新版本API发布后,旧版本API至少维护6个月 -- 废弃API在响应头中添加Warning字段 -- 提供API版本迁移指南 - ---- - -## 四、安全设计 - -### 4.1 认证机制 - -#### 4.1.1 JWT认证 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ JWT认证流程 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ 用户 │────▶│ 登录 │────▶│ 验证 │────▶│ 生成 │ │ -│ │ 登录 │ │ 请求 │ │ 凭证 │ │ Token │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────┐ │ -│ │ 返回 │ │ -│ │ Token │ │ -│ └──────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────┐ │ -│ │ 后续 │ │ -│ │ 请求 │ │ -│ └──────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────┐ │ -│ │ 携带 │ │ -│ │ Token │ │ -│ └──────────┘ │ -│ │ -│ Token结构: │ -│ • Header: 算法、类型 │ -│ • Payload: 用户ID、角色、过期时间 │ -│ • Signature: 签名 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -#### 4.1.2 Token刷新机制 - -- Access Token有效期:2小时 -- Refresh Token有效期:7天 -- Access Token过期时使用Refresh Token刷新 -- Refresh Token过期时需要重新登录 - -### 4.2 权限控制 - -#### 4.2.1 RBAC权限模型 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ RBAC权限模型 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ 用户 │──▶│ 角色 │──▶│ 权限 │──▶│ 资源 │ │ -│ │ User │ │ Role │ │Permission│ │ Resource │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ -│ │ -│ 权限示例: │ -│ • member:view (查看会员) │ -│ • member:edit (编辑会员) │ -│ • member:delete (删除会员) │ -│ • booking:create (创建预约) │ -│ • booking:cancel (取消预约) │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -#### 4.2.2 权限校验流程 - -1. 用户登录后获取角色和权限 -2. 每次请求时校验权限 -3. 无权限时返回403错误 -4. 支持权限继承和组合 - -### 4.3 数据安全 - -#### 4.3.1 数据加密 - -- 敏感数据(手机号、身份证)使用AES-256加密存储 -- 密码使用BCrypt加密 -- 传输数据使用HTTPS加密 -- 数据库连接使用SSL/TLS - -#### 4.3.2 数据脱敏 - -- 手机号脱敏:138****5678 -- 身份证脱敏:110101********1234 -- 银行卡脱敏:6222************1234 - -#### 4.3.3 数据备份 - -- 每日全量备份 -- 每小时增量备份 -- 备份数据加密存储 -- 备份数据异地容灾 - -### 4.4 接口安全 - -#### 4.4.1 防重放攻击 - -- 每个请求携带时间戳 -- 时间戳有效期:5分钟 -- 请求签名验证 - -#### 4.4.2 防SQL注入 - -- 使用参数化查询 -- 使用R2DBC响应式数据库访问 -- 输入参数校验和过滤 - -#### 4.4.3 防XSS攻击 - -- 输入内容过滤和转义 -- 响应头设置Content-Security-Policy -- 使用白名单过滤 - ---- - -## 五、性能设计 - -### 5.1 性能目标 - -| 指标 | 目标值 | 测量方法 | -| -------------- | ---------- | ---------------------- | -| 响应时间 | ≤ 2秒 | 请求响应时间 | -| 并发处理能力 | ≥ 1000 QPS | 每秒处理请求数 | -| 数据库查询时间 | ≤ 100ms | SQL执行时间 | -| 缓存命中率 | ≥ 80% | 缓存命中次数/总请求次数 | -| 系统可用性 | ≥ 99.9% | (总时间-故障时间)/总时间 | - -### 5.2 性能优化策略 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 性能优化策略 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 缓存策略 │ -│ ├── Caffeine本地缓存 (热点数据) │ -│ ├── Redis分布式缓存 (可选) │ -│ ├── 数据库查询结果缓存 │ -│ └── 缓存预热和失效策略 │ -│ │ -│ 2. 数据库优化 │ -│ ├── 索引优化 │ -│ ├── 查询优化 │ -│ ├── 分页查询 │ -│ └── 读写分离 (后期) │ -│ │ -│ 3. 响应式编程 │ -│ ├── WebFlux非阻塞IO │ -│ ├── R2DBC响应式数据库访问 │ -│ └── 异步处理 │ -│ │ -│ 4. 前端优化 │ -│ ├── 资源压缩 │ -│ ├── CDN加速 │ -│ ├── 懒加载 │ -│ └── 防抖节流 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 5.3 高并发场景处理 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 高并发场景处理 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 限流保护 │ -│ └── 令牌桶限流,防止系统过载 │ -│ │ -│ 2. 熔断降级 │ -│ └── 服务异常时快速失败,防止雪崩 │ -│ │ -│ 3. 分布式锁 │ -│ └── Redis分布式锁,保证数据一致性 │ -│ │ -│ 4. 乐观锁 │ -│ └── 版本号控制,冲突时重试 │ -│ │ -│ 5. 排队机制 │ -│ └── 请求进入队列,异步处理结果 │ -│ │ -│ 6. 候补机制 │ -│ └── 满员后自动进入候补队列,有人取消时自动补位 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 六、可扩展性设计 - -### 6.1 水平扩展 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 水平扩展方案 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 无状态设计 │ -│ ├── Session外置到Redis │ -│ ├── 本地不存储用户状态 │ -│ └── 任意实例可处理任意请求 │ -│ │ -│ 2. 负载均衡 │ -│ ├── 轮询: 默认策略 │ -│ ├── 加权轮询: 根据服务器性能分配权重 │ -│ └── 最少连接: 请求分配给连接数最少的服务器 │ -│ │ -│ 3. 服务拆分(后期) │ -│ ├── 会员服务独立部署 │ -│ ├── 预约服务独立部署 │ -│ └── 数据服务独立部署 │ -│ │ -│ 4. 数据库扩展(后期) │ -│ ├── 读写分离 │ -│ ├── 分库分表 │ -│ └── 多活架构 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 6.2 功能扩展 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 功能扩展点 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 支付扩展 │ -│ ├── 预留支付接口抽象 │ -│ ├── 支持微信支付、支付宝、银联等 │ -│ └── 可扩展其他支付渠道 │ -│ │ -│ 2. 硬件扩展 │ -│ ├── 签到网关抽象设计 │ -│ ├── 支持多种签到设备 │ -│ └── 可扩展智能硬件 │ -│ │ -│ 3. 消息扩展 │ -│ ├── 消息模板可配置 │ -│ ├── 支持多渠道推送 │ -│ └── 可扩展新的消息渠道 │ -│ │ -│ 4. 报表扩展 │ -│ ├── 报表模板可配置 │ -│ ├── 支持自定义报表 │ -│ └── 可扩展BI工具对接 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 七、监控与运维 - -### 7.1 监控体系 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 监控体系 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 基础监控 │ -│ ├── CPU使用率 │ -│ ├── 内存使用率 │ -│ ├── 磁盘使用率 │ -│ ├── 网络IO │ -│ └── 进程状态 │ -│ │ -│ 2. 应用监控 │ -│ ├── JVM监控(GC、堆内存、线程) │ -│ ├── HTTP请求监控(QPS、响应时间、错误率) │ -│ ├── 数据库连接池监控 │ -│ └── 缓存命中率监控 │ -│ │ -│ 3. 业务监控 │ -│ ├── 会员注册数 │ -│ ├── 预约成功率 │ -│ ├── 签到成功率 │ -│ └── 支付成功率 │ -│ │ -│ 4. 告警机制 │ -│ ├── 告警规则配置 │ -│ ├── 告警通知方式(邮件、短信、钉钉) │ -│ └── 告警升级策略 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 7.2 日志规范 - -#### 7.2.1 日志级别 - -- ERROR:错误日志,系统异常 -- WARN:警告日志,潜在问题 -- INFO:信息日志,关键业务操作 -- DEBUG:调试日志,开发调试使用 - -#### 7.2.2 日志格式 - -``` -[时间] [级别] [线程] [类名] - 日志内容 -[2026-03-04 10:00:00] [INFO] [http-nio-8080-exec-1] [com.gym.controller.MemberController] - 会员登录成功, memberId=12345 -``` - -#### 7.2.3 日志存储 - -- 日志文件按日期滚动 -- 日志文件保留30天 -- 错误日志单独存储 -- 支持日志查询和导出 - ---- - -## 八、附录 - -### 8.1 技术选型清单 - -| 技术类别 | 技术选型 | 版本 | 用途 | -| -------------- | --------------------------- | -------- | ------------------ | -| 前端框架 | Vue3 | 3.x | 前端开发 | -| 前端构建 | Vite | 5.x | 前端构建 | -| 跨平台框架 | uniapp | 3.x | 小程序开发 | -| 后端框架 | Spring Boot | 3.x | 应用框架 | -| 响应式编程 | Spring WebFlux | 3.x | 响应式Web开发 | -| 数据库 | PostgreSQL | 15+ | 主数据库 | -| 数据库访问 | R2DBC | 1.x | 响应式数据库访问 | -| 缓存 | Caffeine | 3.x | 本地缓存 | -| 缓存(可选) | Redis | 7.x | 分布式缓存 | -| 安全框架 | Spring Security | 6.x | 安全认证授权 | -| 数据库版本管理 | Flyway | 9.x | 数据库版本管理 | -| 容器化 | Docker | 24+ | 容器化部署 | -| 负载均衡 | Nginx | 1.24+ | 反向代理负载均衡 | - -### 8.2 术语表 - -| 术语 | 定义 | -| ----------------------------- | ------------------------------------------------ | -| 租户(Tenant) | 系统的多租户架构中的独立业务实体,如一个连锁品牌 | -| 门店(Store) | 租户下的具体经营场所 | -| 会员(Member) | 在门店注册的用户 | -| 权益(Benefit) | 会员卡包含的时长、次数、储值、等级等权益 | -| 可预约资源(Bookable Resource) | 团课、私教、场地、线上课程等可被预约的对象 | -| 时段(Slot) | 资源的可预约时间窗口 | -| JWT | JSON Web Token,用于身份认证 | -| RBAC | Role-Based Access Control,基于角色的访问控制 | -| QPS | Queries Per Second,每秒查询数 | -| SLA | Service Level Agreement,服务等级协议 | - ---- - -**文档结束** diff --git a/docs/design/OPS-部署运维文档.md b/docs/design/OPS-部署运维文档.md new file mode 100644 index 0000000..9cf27e4 --- /dev/null +++ b/docs/design/OPS-部署运维文档.md @@ -0,0 +1,861 @@ +# 部署运维文档 + +> 文档编号: GYM-OPS-DEPLOY-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建部署运维文档 | + +--- + +## 参考文档 + +- 《健身房管理系统技术架构设计文档》 GYM-HLD-TECH-001 +- 《健身房管理系统响应式编程规范文档》 GYM-STD-REACTIVE-001 +- Docker 官方文档 +- Docker Compose 官方文档 + +--- + +## 一、部署架构 + +### 1.1 部署拓扑 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 部署架构拓扑 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 用户层 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ • 会员小程序 • 教练端App • 管理后台PC │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 负载均衡层 (Nginx) │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ • 负载均衡 • SSL 终止 • 静态资源 • 限流 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 应用层 (Docker Compose) │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ • gym-manage (应用) • postgres (数据库) │ │ +│ │ • redis (缓存) • rabbitmq (消息队列) │ │ +│ │ • elasticsearch (搜索引擎) • prometheus (监控) │ │ +│ │ • grafana (可视化) • kibana (日志可视化) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 监控层 (Prometheus + Grafana) │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ • 指标采集 • 告警规则 • 可视化仪表板 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 服务器配置 + +#### 1.2.1 生产环境配置 + +| 组件 | CPU | 内存 | 磁盘 | 用途 | +|------|------|------|------| +| **应用服务器** | 4 核 | 8GB | 100GB | 运行应用 | +| **数据库服务器** | 8 核 | 16GB | 500GB | PostgreSQL | +| **缓存服务器** | 2 核 | 4GB | 50GB | Redis | +| **消息队列服务器** | 2 核 | 4GB | 100GB | RabbitMQ | +| **搜索服务器** | 4 核 | 8GB | 200GB | Elasticsearch | +| **监控服务器** | 2 核 | 4GB | 50GB | Prometheus + Grafana | + +**推荐配置**: +- 初期:应用 + 数据库 + 缓存部署在同一台服务器(8 核 16GB) +- 中期:应用独立部署(4 核 8GB),数据库独立部署(8 核 16GB) +- 长期:各组件独立部署,提高可用性 + +#### 1.2.2 开发环境配置 + +| 组件 | CPU | 内存 | 磁盘 | 用途 | +|------|------|------|------| +| **开发服务器** | 4 核 | 8GB | 100GB | 开发测试 | + +--- + +## 二、环境准备 + +### 2.1 系统要求 + +#### 2.1.1 操作系统 + +- **推荐**:Ubuntu 20.04 LTS / 22.04 LTS +- **兼容**:CentOS 7+ / Debian 10+ +- **内核版本**:>= 4.15 + +#### 2.1.2 软件依赖 + +| 软件 | 版本 | 用途 | +|------|------|------| +| **Docker** | 24.x+ | 容器化部署 | +| **Docker Compose** | 2.20.x+ | 容器编排 | +| **Git** | 2.30+ | 版本控制 | +| **JDK** | 17+ | 运行环境 | +| **Maven** | 3.9.x+ | 项目构建 | + +### 2.2 环境安装 + +#### 2.2.1 安装 Docker + +```bash +# Ubuntu/Debian +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# 启动 Docker 服务 +sudo systemctl start docker +sudo systemctl enable docker + +# 验证安装 +docker --version +docker info +``` + +#### 2.2.2 安装 Docker Compose + +```bash +# 下载 Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + +# 添加执行权限 +sudo chmod +x /usr/local/bin/docker-compose + +# 验证安装 +docker-compose --version +``` + +#### 2.2.3 安装 JDK + +```bash +# Ubuntu/Debian +sudo apt update +sudo apt install -y openjdk-17-jdk + +# 验证安装 +java -version +``` + +#### 2.2.4 安装 Maven + +```bash +# 下载 Maven +wget https://dlcdn.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz + +# 解压 +tar -xzf apache-maven-3.9.5-bin.tar.gz + +# 移动到 /opt +sudo mv apache-maven-3.9.5 /opt/maven + +# 配置环境变量 +echo 'export PATH=/opt/maven/bin:$PATH' >> ~/.bashrc +source ~/.bashrc + +# 验证安装 +mvn -version +``` + +--- + +## 三、部署流程 + +### 3.1 代码部署 + +#### 3.1.1 克隆代码 + +```bash +# 克隆代码仓库 +git clone +cd gym-manage + +# 查看分支 +git branch -a + +# 切换到生产分支 +git checkout production + +# 拉取最新代码 +git pull origin production +``` + +#### 3.1.2 配置环境变量 + +```bash +# 复制环境变量模板 +cp .env.example .env + +# 编辑环境变量 +vim .env +``` + +**.env 文件示例**: + +```bash +# 数据库配置 +DB_USERNAME=postgres +DB_PASSWORD=your-strong-password + +# Redis 配置 +REDIS_PASSWORD=your-strong-password + +# RabbitMQ 配置 +MQ_USERNAME=admin +MQ_PASSWORD=your-strong-password + +# Grafana 配置 +GRAFANA_USER=admin +GRAFANA_PASSWORD=your-strong-password + +# Spring 配置 +SPRING_PROFILES_ACTIVE=prod + +# JVM 配置 (响应式编程最佳实践) +JAVA_OPTS=-Xms512m -Xmx1024m -XX:+UseZGC -XX:ZAllocationSpikeTolerance=5 -XX:+UnlockExperimentalVMOptions -XX:+UseTransparentHugePages -XX:+AlwaysPreTouch +``` + +#### 3.1.3 构建镜像 + +```bash +# 构建应用镜像 +docker-compose build gym-manage + +# 查看镜像 +docker images | grep gym-manage +``` + +### 3.2 服务部署 + +#### 3.2.1 启动所有服务 + +```bash +# 启动所有服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps + +# 查看日志 +docker-compose logs -f gym-manage +``` + +#### 3.2.2 启动单个服务 + +```bash +# 启动数据库 +docker-compose up -d postgres + +# 启动应用 +docker-compose up -d gym-manage + +# 查看应用日志 +docker-compose logs -f gym-manage +``` + +#### 3.2.3 健康检查 + +```bash +# 检查应用健康状态 +curl http://localhost:8080/actuator/health + +# 检查数据库连接 +docker-compose exec postgres pg_isready -U postgres + +# 检查 Redis 连接 +docker-compose exec redis redis-cli ping + +# 检查 RabbitMQ 连接 +curl http://localhost:15672/api/overview -u admin:admin123 +``` + +### 3.3 数据库初始化 + +#### 3.3.1 创建数据库 + +```bash +# 连接到 PostgreSQL +docker-compose exec postgres psql -U postgres + +# 创建数据库 +CREATE DATABASE gym_manage; + +# 创建用户 +CREATE USER gym_manage WITH PASSWORD 'your-password'; + +# 授权 +GRANT ALL PRIVILEGES ON DATABASE gym_manage TO gym_manage; + +# 退出 +\q +``` + +#### 3.3.2 执行初始化脚本 + +```bash +# 执行初始化脚本 +docker-compose exec -T postgres psql -U postgres -d gym_manage < sql/init.sql +``` + +--- + +## 四、更新部署 + +### 4.1 代码更新 + +#### 4.1.1 拉取最新代码 + +```bash +# 拉取最新代码 +git pull origin production + +# 查看变更 +git log --oneline -5 +``` + +#### 4.1.2 重新构建 + +```bash +# 停止服务 +docker-compose down + +# 重新构建镜像 +docker-compose build gym-manage + +# 启动服务 +docker-compose up -d +``` + +### 4.2 滚动更新 + +#### 4.2.1 零停机更新 + +```bash +# 启动新实例 +docker-compose up -d --scale gym-manage=2 + +# 等待新实例就绪 +sleep 30 + +# 停止旧实例 +docker-compose up -d --scale gym-manage=1 +``` + +### 4.3 回滚部署 + +#### 4.3.1 快速回滚 + +```bash +# 回滚到上一个版本 +git checkout HEAD~1 + +# 重新构建 +docker-compose build gym-manage + +# 启动服务 +docker-compose up -d +``` + +#### 4.3.2 使用 Docker 镜像回滚 + +```bash +# 查看镜像历史 +docker images | grep gym-manage + +# 使用上一个镜像 +docker-compose up -d --no-deps gym-manage +``` + +--- + +## 五、监控运维 + +### 5.1 监控体系 + +#### 5.1.1 Prometheus 监控 + +**访问地址**:http://your-server:9090 + +**主要功能**: +- 指标采集 +- 数据存储 +- 告警规则 +- 查询接口 + +#### 5.1.2 Grafana 可视化 + +**访问地址**:http://your-server:3000 + +**默认账号**: +- 用户名:admin +- 密码:admin123 + +**主要功能**: +- 数据可视化 +- 仪表板配置 +- 告警通知 +- 用户管理 + +#### 5.1.3 Kibana 日志可视化 + +**访问地址**:http://your-server:5601 + +**主要功能**: +- 日志查询 +- 日志分析 +- 可视化图表 +- 告警配置 + +### 5.2 日志管理 + +#### 5.2.1 应用日志 + +```bash +# 查看实时日志 +docker-compose logs -f gym-manage + +# 查看最近 100 行日志 +docker-compose logs --tail=100 gym-manage + +# 查看特定时间的日志 +docker-compose logs --since 2024-01-01T00:00:00 gym-manage +``` + +#### 5.2.2 日志文件 + +```bash +# 查看日志文件 +tail -f logs/gym-manage.log + +# 查看错误日志 +grep ERROR logs/gym-manage.log + +# 统计错误数量 +grep -c ERROR logs/gym-manage.log +``` + +### 5.3 告警配置 + +#### 5.3.1 告警规则 + +**文件位置**:`monitoring/alerts.yml` + +**告警类型**: +- 高错误率 +- 高响应时间 +- 高内存使用率 +- 数据库连接池耗尽 +- 缓存命中率低 + +#### 5.3.2 告警通知 + +**通知方式**: +- 邮件通知 +- 钉钉通知 +- 企业微信通知 +- 短信通知 + +**配置示例**: + +```yaml +alertmanager: + receivers: + - name: 'email' + email_configs: + - to: 'your-email@example.com' + from: 'alertmanager@example.com' + smarthost: 'smtp.example.com:587' + auth_username: 'your-email@example.com' + auth_password: 'your-password' +``` + +--- + +## 六、性能优化 + +### 6.1 应用优化 + +#### 6.1.1 JVM 参数调优 + +```bash +# 生产环境推荐参数 (响应式编程最佳实践) +JAVA_OPTS=-Xms1024m -Xmx2048m -XX:+UseZGC -XX:ZAllocationSpikeTolerance=5 -XX:+UnlockExperimentalVMOptions -XX:+UseTransparentHugePages -XX:+AlwaysPreTouch -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/heapdump.hprof +``` + +**参数说明**: +- `-Xms`:初始堆内存大小 +- `-Xmx`:最大堆内存大小 +- `-XX:+UseZGC`:使用 ZGC 垃圾回收器(响应式编程推荐) +- `-XX:ZAllocationSpikeTolerance`:分配峰值容忍度 +- `-XX:+UnlockExperimentalVMOptions`:解锁实验性选项 +- `-XX:+UseTransparentHugePages`:使用透明大页 +- `-XX:+AlwaysPreTouch`:预分配内存 +- `-XX:+HeapDumpOnOutOfMemoryError`:内存溢出时生成堆转储 +- `-XX:HeapDumpPath`:堆转储文件路径 + +**ZGC 优势**: +- 低延迟:GC 暂停时间通常 < 10ms +- 高吞吐量:适合响应式编程的高并发场景 +- 大堆支持:支持 TB 级堆内存 +- 自适应:自动调整 GC 参数 + +#### 6.1.2 连接池调优 + +```yaml +# application-prod.yml (响应式编程最佳实践) +spring: + r2dbc: + pool: + initial-size: 5 # 初始连接数(响应式编程推荐较少连接) + max-size: 20 # 最大连接数(响应式编程推荐较少连接) + max-idle-time: 30m # 最大空闲时间 + max-life-time: 1h # 最大生命周期 + acquire-timeout: 10s # 获取连接超时时间(响应式编程推荐较长超时) + max-create-connection-time: 30s # 创建连接最大时间 + max-validation-time: 5s # 验证连接最大时间 +``` + +**连接池配置说明**: +- 响应式编程使用较少的连接数(5-20)即可支持高并发 +- 连接获取超时时间设置为 10s,避免快速失败 +- 使用连接池复用,减少连接创建开销 + +### 6.2 数据库优化 + +#### 6.2.1 PostgreSQL 配置(响应式编程优化) + +```bash +# postgresql.conf (响应式编程最佳实践) +# 内存配置 +shared_buffers = 512MB # 共享缓冲区(响应式编程推荐较大值) +effective_cache_size = 2GB # 有效缓存大小 +maintenance_work_mem = 128MB # 维护工作内存 +work_mem = 32MB # 工作内存(响应式编程推荐较大值) + +# WAL 配置 +wal_buffers = 64MB # WAL 缓冲区 +min_wal_size = 2GB # 最小 WAL 大小 +max_wal_size = 8GB # 最大 WAL 大小 +checkpoint_completion_target = 0.9 # 检查点完成目标 + +# 并发配置 +max_connections = 200 # 最大连接数(响应式编程推荐较少连接) +max_worker_processes = 8 # 最大工作进程数 +max_parallel_workers_per_gather = 4 # 每个查询的最大并行工作进程数 +max_parallel_workers = 8 # 最大并行工作进程数 + +# IO 配置 +random_page_cost = 1.1 # 随机页面成本(SSD 优化) +effective_io_concurrency = 300 # 有效 IO 并发数(SSD 优化) +max_io_concurrency = 200 # 最大 IO 并发数 + +# 查询优化 +default_statistics_target = 100 # 默认统计目标 +from_collapse_limit = 8 # FROM 子句折叠限制 +join_collapse_limit = 8 # JOIN 子句折叠限制 + +# 日志配置 +log_min_duration_statement = 1000 # 记录执行时间超过 1s 的语句 +log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h ' # 日志前缀 +log_checkpoints = on # 记录检查点 +log_connections = on # 记录连接 +log_disconnections = on # 记录断开连接 +log_lock_waits = on # 记录锁等待 +``` + +#### 6.2.2 索引优化 + +```sql +-- 查看索引使用情况 +SELECT schemaname, tablename, attname, n_distinct, correlation +FROM pg_stats +WHERE schemaname = 'public' +ORDER BY correlation DESC; + +-- 查看慢查询 +SELECT query, mean_exec_time, calls +FROM pg_stat_statements +ORDER BY mean_exec_time DESC +LIMIT 10; +``` + +### 6.3 缓存优化 + +#### 6.3.1 Redis 配置 + +```bash +# redis.conf +maxmemory 2gb +maxmemory-policy allkeys-lru +save 900 1 +save 300 10 +save 60 10000 +``` + +**参数说明**: +- `maxmemory`:最大内存使用量 +- `maxmemory-policy`:内存淘汰策略 +- `save`:RDB 持久化策略 + +--- + +## 七、故障排查 + +### 7.1 常见问题 + +#### 7.1.1 应用启动失败 + +**症状**:应用无法启动 + +**排查步骤**: + +```bash +# 查看应用日志 +docker-compose logs gym-manage + +# 检查配置文件 +cat application-prod.yml + +# 检查环境变量 +docker-compose config + +# 检查数据库连接 +docker-compose exec postgres pg_isready -U postgres +``` + +**常见原因**: +- 数据库连接失败 +- 配置文件错误 +- 端口冲突 +- 内存不足 + +#### 7.1.2 数据库连接失败 + +**症状**:应用无法连接数据库 + +**排查步骤**: + +```bash +# 检查数据库状态 +docker-compose ps postgres + +# 查看数据库日志 +docker-compose logs postgres + +# 测试数据库连接 +docker-compose exec postgres psql -U postgres -d gym_manage -c "SELECT 1;" + +# 检查网络连接 +docker-compose exec gym-manage ping postgres +``` + +**常见原因**: +- 数据库未启动 +- 网络不通 +- 用户名密码错误 +- 数据库不存在 + +#### 7.1.3 性能下降 + +**症状**:响应时间变长 + +**排查步骤**: + +```bash +# 查看应用日志 +docker-compose logs gym-manage | grep "Slow query" + +# 查看数据库慢查询 +docker-compose exec postgres psql -U postgres -d gym_manage -c "SELECT * FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;" + +# 查看系统资源 +top +htop + +# 查看数据库连接数 +docker-compose exec postgres psql -U postgres -d gym_manage -c "SELECT count(*) FROM pg_stat_activity;" +``` + +**常见原因**: +- 慢查询 +- 数据库连接池耗尽 +- 缓存命中率低 +- 系统资源不足 + +### 7.2 应急处理 + +#### 7.2.1 重启服务 + +```bash +# 重启应用 +docker-compose restart gym-manage + +# 重启数据库 +docker-compose restart postgres + +# 重启所有服务 +docker-compose restart +``` + +#### 7.2.2 回滚版本 + +```bash +# 回滚到上一个版本 +git checkout HEAD~1 + +# 重新构建 +docker-compose build gym-manage + +# 启动服务 +docker-compose up -d +``` + +#### 7.2.3 扩容 + +```bash +# 增加应用实例 +docker-compose up -d --scale gym-manage=2 + +# 增加数据库资源 +docker-compose up -d --scale postgres=2 +``` + +--- + +## 八、备份恢复 + +### 8.1 数据备份 + +#### 8.1.1 数据库备份 + +```bash +# 备份数据库 +docker-compose exec postgres pg_dump -U postgres gym_manage > backup/gym_manage_$(date +%Y%m%d_%H%M%S).sql + +# 压缩备份文件 +gzip backup/gym_manage_$(date +%Y%m%d_%H%M%S).sql +``` + +#### 8.1.2 定时备份 + +```bash +# 添加 crontab 任务 +crontab -e + +# 每天凌晨 2 点备份数据库 +0 2 * * * docker-compose exec -T postgres pg_dump -U postgres gym_manage > backup/gym_manage_$(date +\%Y\%m\%d_\%H\%M\%S).sql + +# 每周日凌晨 3 点清理 7 天前的备份 +0 3 * * 0 find backup -name "gym_manage_*.sql" -mtime +7 -delete +``` + +### 8.2 数据恢复 + +#### 8.2.1 数据库恢复 + +```bash +# 停止应用 +docker-compose stop gym-manage + +# 恢复数据库 +docker-compose exec -T postgres psql -U postgres gym_manage < backup/gym_manage_20240101_020000.sql + +# 启动应用 +docker-compose start gym-manage +``` + +--- + +## 九、安全加固 + +### 9.1 网络安全 + +#### 9.1.1 防火墙配置 + +```bash +# 配置防火墙 +sudo ufw allow 22/tcp # SSH +sudo ufw allow 80/tcp # HTTP +sudo ufw allow 443/tcp # HTTPS +sudo ufw enable +``` + +#### 9.1.2 SSL 证书 + +```bash +# 使用 Let's Encrypt 获取免费 SSL 证书 +sudo apt install certbot +sudo certbot certonly --standalone -d your-domain.com + +# 配置 Nginx SSL +vim nginx/nginx.conf +``` + +### 9.2 应用安全 + +#### 9.2.1 敏感数据加密 + +```bash +# 配置环境变量 +export DB_PASSWORD=$(openssl rand -base64 32) +export REDIS_PASSWORD=$(openssl rand -base64 32) +export MQ_PASSWORD=$(openssl rand -base64 32) +``` + +#### 9.2.2 权限控制 + +```yaml +# application-prod.yml +spring: + security: + user: + name: admin + password: ${ADMIN_PASSWORD} + roles: ADMIN +``` + +--- + +## 十、总结 + +### 10.1 部署要点 + +1. ✅ 使用 Docker Compose 一键部署 +2. ✅ 配置健康检查和自动重启 +3. ✅ 完善的监控和告警体系 +4. ✅ 定期备份数据 +5. ✅ 安全加固和权限控制 + +### 10.2 运维要点 + +1. ✅ 定期查看日志和监控 +2. ✅ 及时处理告警 +3. ✅ 定期备份数据 +4. ✅ 定期更新系统和依赖 +5. ✅ 定期进行安全审计 + +### 10.3 持续改进 + +1. ✅ 性能监控和优化 +2. ✅ 故障复盘和改进 +3. ✅ 文档更新和维护 +4. ✅ 团队培训和知识分享 +5. ✅ 自动化运维工具开发 diff --git a/docs/design/STD-响应式编程规范.md b/docs/design/STD-响应式编程规范.md new file mode 100644 index 0000000..144961e --- /dev/null +++ b/docs/design/STD-响应式编程规范.md @@ -0,0 +1,945 @@ +# 响应式编程规范文档 + +> 文档编号: GYM-STD-REACTIVE-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | ------------------ | +| v1.0 | 2026-03-04 | 张翔 | 创建响应式编程规范文档 | + +--- + +## 参考文档 + +- 《健身房管理系统技术架构设计文档》 GYM-HLD-TECH-001 +- Project Reactor 官方文档 +- Spring WebFlux 官方文档 +- R2DBC 官方文档 + +--- + +## 一、概述 + +### 1.1 目的 + +本文档旨在为健身房管理系统项目制定响应式编程规范,确保团队成员正确使用响应式编程技术栈,避免常见的反模式,提高代码质量和系统性能。 + +### 1.2 适用范围 + +本规范适用于所有使用 Spring WebFlux + R2DBC 技术栈的代码开发。 + +### 1.3 核心原则 + +1. **永不阻塞**:禁止在响应式流中使用阻塞操作 +2. **链式调用**:使用操作符链式调用,避免嵌套 +3. **错误处理**:使用响应式错误处理机制,避免 try-catch +4. **背压处理**:正确处理背压,避免内存溢出 +5. **资源释放**:确保所有资源正确释放,避免资源泄漏 + +--- + +## 二、响应式编程基础 + +### 2.1 核心概念 + +#### 2.1.1 Mono + +**定义**:表示 0-1 个元素的异步序列,返回单个对象或空。 + +**适用场景**: +- 查询单个对象 +- 保存单个对象 +- 更新单个对象 +- 删除单个对象 + +**示例**: + +```java +// 查询单个会员 +public Mono getMember(Long id) { + return memberRepository.findById(id); +} + +// 保存单个会员 +public Mono saveMember(Member member) { + return memberRepository.save(member); +} +``` + +#### 2.1.2 Flux + +**定义**:表示 0-N 个元素的异步序列,返回多个对象。 + +**适用场景**: +- 查询列表 +- 批量操作 +- 流式处理 +- 实时数据推送 + +**示例**: + +```java +// 查询会员列表 +public Flux listMembers(Long tenantId) { + return memberRepository.findByTenantId(tenantId); +} + +// 批量保存会员 +public Flux saveMembers(List members) { + return Flux.fromIterable(members) + .flatMap(memberRepository::save); +} +``` + +#### 2.1.3 Scheduler + +**定义**:控制响应式操作的执行线程。 + +**常用 Scheduler**: + +| Scheduler | 用途 | 示例 | +|-----------|------|------| +| **Schedulers.parallel()** | CPU 密集型操作 | 数据计算、转换 | +| **Schedulers.boundedElastic()** | 阻塞 I/O 操作 | 文件读写、网络请求 | +| **Schedulers.single()** | 单线程顺序执行 | 顺序处理任务 | +| **Schedulers.immediate()** | 当前线程执行 | 简单操作 | + +**示例**: + +```java +// CPU 密集型操作 +public Flux processMembers(Flux members) { + return members.publishOn(Schedulers.parallel()) + .map(this::calculateLevel); +} + +// 阻塞 I/O 操作 +public Mono readFile(String path) { + return Mono.fromCallable(() -> Files.readString(Paths.get(path))) + .subscribeOn(Schedulers.boundedElastic()); +} +``` + +### 2.2 常用操作符 + +#### 2.2.1 转换操作符 + +| 操作符 | 功能 | 示例 | +|-------|------|------| +| **map** | 一对一转换 | `.map(member -> member.getName())` | +| **flatMap** | 一对多转换(异步) | `.flatMap(member -> loadCards(member.getId()))` | +| **flatMapMany** | 一对多转换(返回 Flux) | `.flatMapMany(member -> listBenefits(member.getId()))` | +| **filter** | 过滤元素 | `.filter(member -> member.getStatus() == 1)` | + +**示例**: + +```java +// map:一对一转换 +public Flux getMemberNames(Long tenantId) { + return memberRepository.findByTenantId(tenantId) + .map(Member::getName); +} + +// flatMap:一对多转换(异步) +public Mono getMemberWithCards(Long id) { + return memberRepository.findById(id) + .flatMap(member -> memberCardRepository.findByMemberId(member.getId()) + .collectList() + .map(cards -> { + member.setCards(cards); + return member; + })); +} + +// filter:过滤元素 +public Flux getActiveMembers(Long tenantId) { + return memberRepository.findByTenantId(tenantId) + .filter(member -> member.getStatus() == 1); +} +``` + +#### 2.2.2 条件操作符 + +| 操作符 | 功能 | 示例 | +|-------|------|------| +| **switchIfEmpty** | 序列为空时返回备选 | `.switchIfEmpty(Mono.error(new BusinessException("会员不存在")))` | +| **defaultIfEmpty** | 序列为空时返回默认值 | `.defaultIfEmpty(Member.builder().build())` | +| **take** | 取前 N 个元素 | `.take(10)` | +| **skip** | 跳过前 N 个元素 | `.skip(10)` | + +**示例**: + +```java +// switchIfEmpty:序列为空时返回备选 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .switchIfEmpty(Mono.error(new BusinessException("会员不存在"))); +} + +// defaultIfEmpty:序列为空时返回默认值 +public Flux listMembers(Long tenantId) { + return memberRepository.findByTenantId(tenantId) + .defaultIfEmpty(Member.builder().build()); +} + +// take:取前 10 个元素 +public Flux listMembers(Long tenantId, int limit) { + return memberRepository.findByTenantId(tenantId) + .take(limit); +} +``` + +#### 2.2.3 错误处理操作符 + +| 操作符 | 功能 | 示例 | +|-------|------|------| +| **onErrorResume** | 捕获错误并返回备选序列 | `.onErrorResume(e -> Mono.empty())` | +| **onErrorReturn** | 捕获错误并返回默认值 | `.onErrorReturn(Member.builder().build())` | +| **doOnError** | 错误时执行副作用 | `.doOnError(e -> log.error("查询失败", e))` | +| **retry** | 重试 | `.retry(3)` | +| **retryWhen** | 高级重试 | `.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))` | + +**示例**: + +```java +// onErrorResume:捕获错误并返回备选序列 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .onErrorResume(DataAccessException.class, e -> { + log.error("数据库查询失败: memberId={}", id, e); + return Mono.empty(); + }); +} + +// onErrorReturn:捕获错误并返回默认值 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .onErrorReturn(Member.builder().build()); +} + +// doOnError:错误时执行副作用 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .doOnError(e -> log.error("查询会员失败: memberId={}", id, e)); +} + +// retry:重试 3 次 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .retry(3); +} + +// retryWhen:高级重试(指数退避) +public Mono getMember(Long id) { + return memberRepository.findById(id) + .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) + .filter(throwable -> throwable instanceof TimeoutException) + .doBeforeRetry(signal -> log.warn("重试: attempt={}", signal.totalRetries()))); +} +``` + +#### 2.2.4 生命周期操作符 + +| 操作符 | 功能 | 示例 | +|-------|------|------| +| **doOnSubscribe** | 订阅时执行 | `.doOnSubscribe(s -> log.debug("开始查询"))` | +| **doOnNext** | 每个元素到达时执行 | `.doOnNext(member -> log.debug("查询到会员: {}", member.getName()))` | +| **doOnComplete** | 完成时执行 | `.doOnComplete(() -> log.debug("查询完成"))` | +| **doOnError** | 错误时执行 | `.doOnError(e -> log.error("查询失败", e))` | +| **doOnTerminate** | 终止时执行(无论成功或失败) | `.doOnTerminate(() -> log.debug("查询结束"))` | + +**示例**: + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .doOnSubscribe(s -> log.debug("开始查询会员: memberId={}", id)) + .doOnNext(member -> log.debug("查询到会员: memberId={}, name={}", member.getId(), member.getName())) + .doOnComplete(() -> log.debug("查询会员完成: memberId={}", id)) + .doOnError(e -> log.error("查询会员失败: memberId={}", id, e)) + .doOnTerminate(() -> log.debug("查询会员结束: memberId={}", id)); +} +``` + +--- + +## 三、编码规范 + +### 3.1 基本原则 + +#### 3.1.1 永不阻塞 + +**规则**:禁止在响应式流中使用阻塞操作。 + +**✅ 正确示例**: + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> loadMemberCards(member.getId())); +} +``` + +**❌ 错误示例**: + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> { + // 错误:使用 block() 阻塞 + List cards = memberCardRepository.findByMemberId(member.getId()) + .collectList().block(); + member.setCards(cards); + return Mono.just(member); + }); +} +``` + +#### 3.1.2 链式调用 + +**规则**:使用操作符链式调用,避免嵌套。 + +**✅ 正确示例**: + +```java +public Mono getMemberWithCardsAndBenefits(Long id) { + return memberRepository.findById(id) + .flatMap(member -> loadMemberCards(member.getId()) + .map(cards -> { + member.setCards(cards); + return member; + })) + .flatMap(member -> loadMemberBenefits(member.getId()) + .map(benefits -> { + member.setBenefits(benefits); + return member; + })); +} +``` + +**❌ 错误示例**: + +```java +public Mono getMemberWithCardsAndBenefits(Long id) { + return memberRepository.findById(id) + .flatMap(member -> { + return loadMemberCards(member.getId()) + .map(cards -> { + member.setCards(cards); + return member; + }) + .flatMap(memberWithCards -> { + return loadMemberBenefits(memberWithCards.getId()) + .map(benefits -> { + memberWithCards.setBenefits(benefits); + return memberWithCards; + }); + }); + }); +} +``` + +#### 3.1.3 错误处理 + +**规则**:使用响应式错误处理机制,避免 try-catch。 + +**✅ 正确示例**: + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .switchIfEmpty(Mono.error(new BusinessException("会员不存在"))) + .onErrorResume(DataAccessException.class, e -> { + log.error("数据库查询失败: memberId={}", id, e); + return Mono.error(new SystemException("系统错误")); + }); +} +``` + +**❌ 错误示例**: + +```java +public Mono getMember(Long id) { + try { + return memberRepository.findById(id) + .switchIfEmpty(Mono.error(new BusinessException("会员不存在"))); + } catch (Exception e) { + // 错误:try-catch 无法捕获响应式异常 + log.error("查询失败", e); + return Mono.error(new SystemException("系统错误")); + } +} +``` + +### 3.2 Service 层规范 + +#### 3.2.1 基本结构 + +```java +@Service +@Slf4j +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + private final MemberCardRepository memberCardRepository; + private final BenefitService benefitService; + + /** + * 查询会员 + */ + public Mono getMember(Long id) { + return memberRepository.findById(id) + .switchIfEmpty(Mono.error(new BusinessException("会员不存在"))) + .doOnSubscribe(s -> log.debug("开始查询会员: memberId={}", id)) + .doOnNext(member -> log.debug("查询到会员: memberId={}, name={}", member.getId(), member.getName())) + .doOnError(e -> log.error("查询会员失败: memberId={}", id, e)) + .doOnTerminate(() -> log.debug("查询会员结束: memberId={}", id)); + } + + /** + * 查询会员列表 + */ + public Flux listMembers(Long tenantId, Long storeId) { + return memberRepository.findByTenantIdAndStoreId(tenantId, storeId) + .filter(member -> member.getStatus() == 1) + .sort(Comparator.comparing(Member::getCreatedAt).reversed()); + } + + /** + * 创建会员 + */ + @Transactional + public Mono createMember(MemberCreateRequest request) { + return validateMemberCreateRequest(request) + .flatMap(v -> buildMember(request)) + .flatMap(memberRepository::save) + .flatMap(member -> createDefaultMemberCard(member)) + .doOnSuccess(member -> log.info("创建会员成功: memberId={}", member.getId())) + .doOnError(e -> log.error("创建会员失败: {}", e.getMessage())); + } + + private Mono validateMemberCreateRequest(MemberCreateRequest request) { + return memberRepository.findByPhoneAndTenantId(request.getPhone(), request.getTenantId()) + .flatMap(existing -> Mono.error(new BusinessException("手机号已注册"))) + .switchIfEmpty(Mono.empty()); + } + + private Mono buildMember(MemberCreateRequest request) { + Member member = Member.builder() + .tenantId(request.getTenantId()) + .storeId(request.getStoreId()) + .memberNo(generateMemberNo(request.getTenantId())) + .name(request.getName()) + .phone(encryptPhone(request.getPhone())) + .phoneMask(maskPhone(request.getPhone())) + .gender(request.getGender()) + .birthday(request.getBirthday()) + .status(1) + .build(); + + return Mono.just(member); + } + + private Mono createDefaultMemberCard(Member member) { + MemberCard card = MemberCard.builder() + .tenantId(member.getTenantId()) + .memberId(member.getId()) + .cardNo(generateCardNo(member.getTenantId())) + .status(1) + .build(); + + return memberCardRepository.save(card) + .thenReturn(member); + } +} +``` + +#### 3.2.2 事务管理 + +```java +@Service +@Slf4j +public class BookingService { + + private final BookingRecordRepository bookingRecordRepository; + private final BookingSlotRepository bookingSlotRepository; + private final BenefitService benefitService; + + /** + * 预约时段 + */ + @Transactional + public Mono 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())) + .doOnSuccess(booking -> log.info("预约成功: bookingId={}", booking.getId())) + .doOnError(e -> log.error("预约失败: {}", e.getMessage())); + } + + private Mono checkSlotAvailability(Long slotId) { + return bookingSlotRepository.findById(slotId) + .switchIfEmpty(Mono.error(new BusinessException("时段不存在"))) + .filter(slot -> slot.getStatus() == 1) + .switchIfEmpty(Mono.error(new BusinessException("时段不可预约"))) + .filter(slot -> slot.getBookedCount() < slot.getCapacity()) + .switchIfEmpty(Mono.error(new BusinessException("时段已满"))); + } + + private Mono deductBenefit(Long memberId, BookingSlot slot) { + return benefitService.deductBenefit(memberId, + slot.getPriceType(), slot.getPriceValue()) + .switchIfEmpty(Mono.error(new BusinessException("权益不足"))); + } + + private Mono createBookingRecord(BookingRequest request, + MemberBenefit benefit) { + BookingRecord record = BookingRecord.builder() + .tenantId(request.getTenantId()) + .storeId(request.getStoreId()) + .memberId(request.getMemberId()) + .slotId(request.getSlotId()) + .bookingNo(generateBookingNo(request.getTenantId())) + .status(1) + .benefitId(benefit.getId()) + .build(); + + return bookingRecordRepository.save(record); + } + + private Mono updateSlotBookedCount(Long slotId) { + return bookingSlotRepository.findById(slotId) + .flatMap(slot -> { + slot.setBookedCount(slot.getBookedCount() + 1); + if (slot.getBookedCount() >= slot.getCapacity()) { + slot.setStatus(2); // 已满 + } + return bookingSlotRepository.save(slot); + }) + .then(); + } +} +``` + +### 3.3 Controller 层规范 + +#### 3.3.1 基本结构 + +```java +@RestController +@RequestMapping("/members") +@RequiredArgsConstructor +@Slf4j +public class MemberController { + + private final MemberService memberService; + + /** + * 查询会员 + */ + @GetMapping("/{id}") + public Mono>> getMember(@PathVariable Long id) { + return memberService.getMember(id) + .map(member -> ResponseEntity.ok(ApiResponse.success(member))) + .switchIfEmpty(Mono.just(ResponseEntity.notFound().build())) + .onErrorResume(BusinessException.class, e -> + Mono.just(ResponseEntity.badRequest() + .body(ApiResponse.error(e.getMessage())))) + .onErrorResume(Exception.class, e -> { + log.error("查询会员失败: memberId={}", id, e); + return Mono.just(ResponseEntity.internalServerError() + .body(ApiResponse.error("系统错误"))); + }); + } + + /** + * 查询会员列表 + */ + @GetMapping + public Mono>>> listMembers( + @RequestParam(required = false) Long tenantId, + @RequestParam(required = false) Long storeId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + return memberService.listMembers(tenantId, storeId, page, size) + .map(members -> ResponseEntity.ok(ApiResponse.success(members))); + } + + /** + * 创建会员 + */ + @PostMapping + public Mono>> createMember( + @Valid @RequestBody MemberCreateRequest request) { + return memberService.createMember(request) + .map(member -> ResponseEntity.ok(ApiResponse.success(member))) + .onErrorResume(BusinessException.class, e -> + Mono.just(ResponseEntity.badRequest() + .body(ApiResponse.error(e.getMessage())))) + .onErrorResume(Exception.class, e -> { + log.error("创建会员失败", e); + return Mono.just(ResponseEntity.internalServerError() + .body(ApiResponse.error("系统错误"))); + }); + } +} +``` + +--- + +## 四、反模式 + +### 4.1 阻塞操作 + +**❌ 反模式**:在响应式流中使用 `block()`、`blockFirst()`、`blockLast()` + +```java +// 错误示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> { + // 错误:使用 block() 阻塞 + List cards = memberCardRepository.findByMemberId(member.getId()) + .collectList().block(); + member.setCards(cards); + return Mono.just(member); + }); +} +``` + +**✅ 正确做法**:使用 `flatMap` 链式调用 + +```java +// 正确示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> memberCardRepository.findByMemberId(member.getId()) + .collectList() + .map(cards -> { + member.setCards(cards); + return member; + })); +} +``` + +### 4.2 嵌套订阅 + +**❌ 反模式**:在 `flatMap` 中使用 `subscribe` + +```java +// 错误示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .flatMap(member -> { + memberCardRepository.findByMemberId(member.getId()) + .collectList() + .subscribe(cards -> { + // 错误:在 flatMap 中使用 subscribe + member.setCards(cards); + }); + return Mono.just(member); + }); +} +``` + +**✅ 正确做法**:使用 `map` 转换数据 + +```java +// 正确示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .zipWith(memberCardRepository.findByMemberId(id).collectList()) + .map(tuple -> { + Member member = tuple.getT1(); + List cards = tuple.getT2(); + member.setCards(cards); + return member; + }); +} +``` + +### 4.3 忽略错误 + +**❌ 反模式**:忽略错误,不处理 + +```java +// 错误示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .onErrorResume(e -> Mono.empty()); // 错误:忽略错误 +} +``` + +**✅ 正确做法**:记录错误并处理 + +```java +// 正确示例 +public Mono getMember(Long id) { + return memberRepository.findById(id) + .onErrorResume(e -> { + log.error("查询会员失败: memberId={}", id, e); + return Mono.error(new SystemException("系统错误")); + }); +} +``` + +### 4.4 不处理背压 + +**❌ 反模式**:不处理背压,可能导致内存溢出 + +```java +// 错误示例 +public Flux listAllMembers() { + return memberRepository.findAll(); // 错误:可能返回大量数据 +} +``` + +**✅ 正确做法**:使用 `take` 限制数据量 + +```java +// 正确示例 +public Flux listAllMembers() { + return memberRepository.findAll() + .take(1000); // 限制最多返回 1000 条 +} +``` + +### 4.5 资源泄漏 + +**❌ 反模式**:不释放资源 + +```java +// 错误示例 +public Mono readFile(String path) { + return Mono.fromCallable(() -> { + // 错误:不释放资源 + BufferedReader reader = Files.newBufferedReader(Paths.get(path)); + return reader.readLine(); + }); +} +``` + +**✅ 正确做法**:使用 `using` 确保资源释放 + +```java +// 正确示例 +public Mono readFile(String path) { + return Mono.using( + () -> Files.newBufferedReader(Paths.get(path)), + reader -> Mono.fromCallable(reader::readLine), + reader -> { + try { + reader.close(); + } catch (IOException e) { + log.error("关闭文件失败", e); + } + }); +} +``` + +--- + +## 五、最佳实践 + +### 5.1 日志记录 + +**原则**:使用 `doOnSubscribe`、`doOnNext`、`doOnError`、`doOnTerminate` 记录关键操作。 + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .doOnSubscribe(s -> log.debug("开始查询会员: memberId={}", id)) + .doOnNext(member -> log.debug("查询到会员: memberId={}, name={}", member.getId(), member.getName())) + .doOnComplete(() -> log.debug("查询会员完成: memberId={}", id)) + .doOnError(e -> log.error("查询会员失败: memberId={}", id, e)) + .doOnTerminate(() -> log.debug("查询会员结束: memberId={}", id)); +} +``` + +### 5.2 超时控制 + +**原则**:为所有外部调用设置超时时间。 + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .timeout(Duration.ofSeconds(3)) // 3 秒超时 + .switchIfEmpty(Mono.error(new BusinessException("会员不存在"))); +} +``` + +### 5.3 重试机制 + +**原则**:为可重试的操作设置重试机制。 + +```java +public Mono getMember(Long id) { + return memberRepository.findById(id) + .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) // 重试 3 次,间隔 1 秒 + .filter(throwable -> throwable instanceof TimeoutException) + .doBeforeRetry(signal -> log.warn("重试: attempt={}", signal.totalRetries()))); +} +``` + +### 5.4 缓存策略 + +**原则**:使用 Cache-Aside 模式,先查缓存,缓存未命中再查数据库。 + +```java +public Mono 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)) + ); +} +``` + +### 5.5 性能优化 + +**原则**:使用 `parallel()` 并行处理 CPU 密集型操作。 + +```java +public Flux processMembers(Flux members) { + return members.publishOn(Schedulers.parallel()) + .map(this::calculateLevel) + .publishOn(Schedulers.boundedElastic()); +} +``` + +--- + +## 六、测试规范 + +### 6.1 单元测试 + +**原则**:使用 `StepVerifier` 测试响应式流。 + +```java +@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(); + } + + @Test + void testGetMemberNotFound() { + when(memberRepository.findById(1L)) + .thenReturn(Mono.empty()); + + StepVerifier.create(memberService.getMember(1L)) + .expectErrorMatches(e -> e instanceof BusinessException) + .verify(); + } +} +``` + +### 6.2 集成测试 + +**原则**:使用 `WebTestClient` 测试 Controller。 + +```java +@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("张三"); + }); + } + + @Test + void testGetMemberNotFound() { + webTestClient.get() + .uri("/api/v1/members/999") + .exchange() + .expectStatus().isNotFound(); + } +} +``` + +### 6.3 性能测试 + +**原则**:使用 `StepVerifier.withVirtualTime` 测试性能。 + +```java +@Test +void testGetMemberPerformance() { + StepVerifier.withVirtualTime(() -> memberService.getMember(1L)) + .expectNextCount(1) + .expectComplete() + .verify(Duration.ofMillis(100)); // 100ms 内完成 +} +``` + +--- + +## 七、总结 + +### 7.1 核心原则回顾 + +1. ✅ **永不阻塞**:禁止在响应式流中使用阻塞操作 +2. ✅ **链式调用**:使用操作符链式调用,避免嵌套 +3. ✅ **错误处理**:使用响应式错误处理机制,避免 try-catch +4. ✅ **背压处理**:正确处理背压,避免内存溢出 +5. ✅ **资源释放**:确保所有资源正确释放,避免资源泄漏 + +### 7.2 关键成功因素 + +1. ✅ 严格遵守响应式编程规范 +2. ✅ 使用 StepVerifier 进行测试 +3. ✅ 完善的日志记录 +4. ✅ 合理的超时和重试机制 +5. ✅ 正确的缓存策略 + +### 7.3 持续改进 + +1. ✅ 定期代码审查 +2. ✅ 性能监控和优化 +3. ✅ 技术分享和培训 +4. ✅ 文档更新和维护 diff --git a/docs/design/LLD-会员模块详细设计.md b/docs/design/modules/LLD-会员模块详细设计.md similarity index 99% rename from docs/design/LLD-会员模块详细设计.md rename to docs/design/modules/LLD-会员模块详细设计.md index e16c3d3..ede5db3 100644 --- a/docs/design/LLD-会员模块详细设计.md +++ b/docs/design/modules/LLD-会员模块详细设计.md @@ -4,7 +4,10 @@ > 版本: v1.0 > 日期: 2026-02-28 > 作者: 张翔 -> 状态: 初稿 +> 状态: 初稿 +> **归属版本**: 基础版 + +**说明**:本文档为健身房管理系统**基础版**的会员模块详细设计文档,描述会员管理模块的数据库设计、API设计、业务逻辑实现等技术细节。 --- diff --git a/docs/design/LLD-签到模块详细设计.md b/docs/design/modules/LLD-签到模块详细设计.md similarity index 99% rename from docs/design/LLD-签到模块详细设计.md rename to docs/design/modules/LLD-签到模块详细设计.md index 9b4d481..d0a3551 100644 --- a/docs/design/LLD-签到模块详细设计.md +++ b/docs/design/modules/LLD-签到模块详细设计.md @@ -4,7 +4,10 @@ > 版本: v1.0 > 日期: 2026-02-28 > 作者: 张翔 -> 状态: 初稿 +> 状态: 初稿 +> **归属版本**: 基础版 + +**说明**:本文档为健身房管理系统**基础版**的签到模块详细设计文档,描述扫码签到模块的数据库设计、API设计、业务逻辑实现等技术细节。 --- diff --git a/docs/design/LLD-预约模块详细设计.md b/docs/design/modules/LLD-预约模块详细设计.md similarity index 99% rename from docs/design/LLD-预约模块详细设计.md rename to docs/design/modules/LLD-预约模块详细设计.md index 006ec36..3974adb 100644 --- a/docs/design/LLD-预约模块详细设计.md +++ b/docs/design/modules/LLD-预约模块详细设计.md @@ -4,7 +4,10 @@ > 版本: v1.0 > 日期: 2026-02-28 > 作者: 张翔 -> 状态: 初稿 +> 状态: 初稿 +> **归属版本**: 基础版 + +**说明**:本文档为健身房管理系统**基础版**的预约模块详细设计文档,描述团课预约模块的数据库设计、API设计、业务逻辑实现等技术细节。 --- @@ -820,7 +823,6 @@ public class BookingDomainService { private final BenefitDomainService benefitService; private final TransactionalOperator rxtx; - @Transactional public Mono createBooking(Long memberId, Long slotId, String source) { return Mono.defer(() -> slotRepository.findById(slotId) @@ -868,7 +870,6 @@ public class BookingDomainService { ).as(rxtx::transactional); } - @Transactional public Mono cancelBooking(Long bookingId, String reason, Long operatorId) { return Mono.defer(() -> recordRepository.findById(bookingId) diff --git a/docs/design/前端安全规范.md b/docs/design/前端安全规范.md new file mode 100644 index 0000000..6a6940e --- /dev/null +++ b/docs/design/前端安全规范.md @@ -0,0 +1,958 @@ +# 健身房管理系统前端安全规范文档 + +> 文档编号: GYM-FE-SEC-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-04 | 张翔 | 创建前端安全规范 | + +--- + +## 参考文档 + +- 《健身房管理系统前端技术架构详细设计》 GYM-FE-ARCH-001 +- OWASP Top 10 Web Application Security Risks +- Content Security Policy (CSP) Level 3 +- Web Content Accessibility Guidelines (WCAG) 2.1 + +--- + +## 一、安全概述 + +### 1.1 安全目标 + +- **数据安全**:保护用户敏感数据(手机号、身份证号、银行卡号等) +- **交易安全**:确保支付和充值操作的安全性 +- **身份安全**:防止未授权访问和身份冒用 +- **隐私保护**:符合GDPR等隐私法规要求 +- **合规性**:符合金融行业安全标准和监管要求 + +### 1.2 安全原则 + +| 原则 | 描述 | 实施方式 | +|------|------|----------| +| **最小权限** | 只授予必要的权限 | 基于角色的访问控制(RBAC) | +| **纵深防御** | 多层安全防护 | 输入验证、输出转义、加密传输 | +| **默认安全** | 默认配置安全 | CSP策略、安全Headers | +| **审计追踪** | 记录关键操作 | 操作日志、异常日志 | +| **持续监控** | 实时安全监控 | 错误监控、性能监控 | + +### 1.3 安全威胁 + +| 威胁类型 | 风险等级 | 防护措施 | +|---------|---------|----------| +| **XSS(跨站脚本攻击)** | 高 | 输入过滤、输出转义、CSP | +| **CSRF(跨站请求伪造)** | 高 | Token验证、SameSite Cookie | +| **点击劫持** | 中 | X-Frame-Options、CSP | +| **中间人攻击** | 高 | HTTPS、HSTS | +| **敏感信息泄露** | 高 | 数据加密、脱敏显示 | +| **暴力破解** | 中 | 验证码、登录限制 | +| **会话劫持** | 高 | 安全Cookie、会话超时 | + +--- + +## 二、XSS防护 + +### 2.1 输入验证 + +#### 2.1.1 白名单验证 + +```typescript +// utils/validator.ts +export function sanitizeInput(input: string, allowedChars: RegExp): string { + return input.replace(allowedChars, '') +} + +export function validatePhoneNumber(phone: string): boolean { + const phoneRegex = /^1[3-9]\d{9}$/ + return phoneRegex.test(phone) +} + +export function validateIdCard(idCard: string): boolean { + const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/ + return idCardRegex.test(idCard) +} + +export function validateEmail(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) +} +``` + +#### 2.1.2 输入长度限制 + +```typescript +// utils/validator.ts +export const MAX_INPUT_LENGTH = { + name: 64, + phone: 11, + idCard: 18, + address: 256, + remark: 512 +} + +export function validateLength(input: string, maxLength: number): boolean { + return input.length <= maxLength +} +``` + +### 2.2 输出转义 + +#### 2.2.1 HTML转义 + +```typescript +// utils/sanitize.ts +import DOMPurify from 'dompurify' + +export function sanitizeHtml(html: string): string { + return DOMPurify.sanitize(html, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u'], + ALLOWED_ATTR: [] + }) +} + +export function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + return text.replace(/[&<>"']/g, (m) => map[m]) +} +``` + +#### 2.2.2 URL转义 + +```typescript +// utils/sanitize.ts +export function sanitizeUrl(url: string): string { + try { + const parsed = new URL(url) + if (!['http:', 'https:'].includes(parsed.protocol)) { + return '#' + } + return url + } catch { + return '#' + } +} +``` + +### 2.3 CSP策略 + +#### 2.3.1 基础CSP配置 + +```html + + +``` + +#### 2.3.2 动态CSP配置 + +```typescript +// utils/csp.ts +export function generateCSP(config: CSPConfig): string { + const directives = [ + `default-src ${config.defaultSrc.join(' ')}`, + `script-src ${config.scriptSrc.join(' ')}`, + `style-src ${config.styleSrc.join(' ')}`, + `img-src ${config.imgSrc.join(' ')}`, + `connect-src ${config.connectSrc.join(' ')}` + ] + + return directives.join('; ') +} + +// 使用 +const csp = generateCSP({ + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "https://cdn.jsdelivr.net"], + styleSrc: ["'self'", "'unsafe-inline'"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'", "https://api.example.com"] +}) + +document.querySelector('meta[http-equiv="Content-Security-Policy"]') + ?.setAttribute('content', csp) +``` + +--- + +## 三、CSRF防护 + +### 3.1 Token验证 + +#### 3.1.1 Token生成与存储 + +```typescript +// utils/csrf.ts +export function generateCSRFToken(): string { + const array = new Uint8Array(32) + crypto.getRandomValues(array) + return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('') +} + +export function setCSRFToken(token: string): void { + localStorage.setItem('csrf_token', token) +} + +export function getCSRFToken(): string { + return localStorage.getItem('csrf_token') || '' +} +``` + +#### 3.1.2 Token注入请求 + +```typescript +// api/request.ts +import { getCSRFToken } from '@/utils/csrf' + +instance.interceptors.request.use((config) => { + const csrfToken = getCSRFToken() + if (csrfToken) { + config.headers['X-CSRF-Token'] = csrfToken + } + return config +}) +``` + +### 3.2 SameSite Cookie + +```typescript +// api/request.ts +instance.interceptors.request.use((config) => { + config.withCredentials = true + return config +}) +``` + +### 3.3 双重Cookie提交 + +```typescript +// utils/csrf.ts +export function setDoubleSubmitCookie(token: string): void { + document.cookie = `csrf_token=${token}; path=/; SameSite=Strict; Secure` +} + +export function getDoubleSubmitCookie(): string { + const match = document.cookie.match(/csrf_token=([^;]+)/) + return match ? match[1] : '' +} +``` + +--- + +## 四、数据安全 + +### 4.1 数据加密 + +#### 4.1.1 AES加密 + +```typescript +// utils/crypto.ts +import CryptoJS from 'crypto-js' + +const SECRET_KEY = import.meta.env.VITE_CRYPTO_SECRET_KEY + +export function encrypt(text: string): string { + const key = CryptoJS.enc.Utf8.parse(SECRET_KEY) + const iv = CryptoJS.lib.WordArray.random(16) + const encrypted = CryptoJS.AES.encrypt(text, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }) + return iv.toString() + ':' + encrypted.toString() +} + +export function decrypt(ciphertext: string): string { + const key = CryptoJS.enc.Utf8.parse(SECRET_KEY) + const [ivHex, encrypted] = ciphertext.split(':') + const iv = CryptoJS.enc.Hex.parse(ivHex) + const decrypted = CryptoJS.AES.decrypt(encrypted, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }) + return decrypted.toString(CryptoJS.enc.Utf8) +} +``` + +#### 4.1.2 RSA加密(用于敏感数据) + +```typescript +// utils/crypto.ts +import JSEncrypt from 'jsencrypt' + +const PUBLIC_KEY = import.meta.env.VITE_RSA_PUBLIC_KEY + +export function encryptWithRSA(text: string): string { + const encrypt = new JSEncrypt() + encrypt.setPublicKey(PUBLIC_KEY) + return encrypt.encrypt(text) || '' +} +``` + +### 4.2 数据脱敏 + +#### 4.2.1 手机号脱敏 + +```typescript +// utils/mask.ts +export function maskPhone(phone: string): string { + if (!phone || phone.length !== 11) { + return phone + } + return phone.substring(0, 3) + '****' + phone.substring(7) +} +``` + +#### 4.2.2 身份证号脱敏 + +```typescript +// utils/mask.ts +export function maskIdCard(idCard: string): string { + if (!idCard || idCard.length !== 18) { + return idCard + } + return idCard.substring(0, 6) + '********' + idCard.substring(14) +} +``` + +#### 4.2.3 银行卡号脱敏 + +```typescript +// utils/mask.ts +export function maskBankCard(bankCard: string): string { + if (!bankCard || bankCard.length < 16) { + return bankCard + } + return bankCard.substring(0, 4) + ' **** **** ' + bankCard.substring(bankCard.length - 4) +} +``` + +### 4.3 敏感信息存储 + +#### 4.3.1 安全存储 + +```typescript +// utils/storage.ts +import { encrypt, decrypt } from './crypto' + +export const secureStorage = { + setItem(key: string, value: any): void { + const encrypted = encrypt(JSON.stringify(value)) + localStorage.setItem(key, encrypted) + }, + + getItem(key: string): T | null { + const encrypted = localStorage.getItem(key) + if (!encrypted) return null + try { + return JSON.parse(decrypt(encrypted)) as T + } catch { + return null + } + }, + + removeItem(key: string): void { + localStorage.removeItem(key) + }, + + clear(): void { + localStorage.clear() + } +} +``` + +#### 4.3.2 会话存储 + +```typescript +// utils/storage.ts +export const sessionStorage = { + setItem(key: string, value: any): void { + const encrypted = encrypt(JSON.stringify(value)) + window.sessionStorage.setItem(key, encrypted) + }, + + getItem(key: string): T | null { + const encrypted = window.sessionStorage.getItem(key) + if (!encrypted) return null + try { + return JSON.parse(decrypt(encrypted)) as T + } catch { + return null + } + }, + + removeItem(key: string): void { + window.sessionStorage.removeItem(key) + }, + + clear(): void { + window.sessionStorage.clear() + } +} +``` + +--- + +## 五、身份认证与授权 + +### 5.1 认证安全 + +#### 5.1.1 密码安全 + +```typescript +// utils/password.ts +export function validatePassword(password: string): { valid: boolean; message?: string } { + if (password.length < 8) { + return { valid: false, message: '密码长度至少8位' } + } + + if (!/[A-Z]/.test(password)) { + return { valid: false, message: '密码必须包含大写字母' } + } + + if (!/[a-z]/.test(password)) { + return { valid: false, message: '密码必须包含小写字母' } + } + + if (!/[0-9]/.test(password)) { + return { valid: false, message: '密码必须包含数字' } + } + + if (!/[!@#$%^&*]/.test(password)) { + return { valid: false, message: '密码必须包含特殊字符' } + } + + return { valid: true } +} +``` + +#### 5.1.2 Token管理 + +```typescript +// stores/auth.ts +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useAuthStore = defineStore('auth', () => { + const token = ref('') + const refreshToken = ref('') + const tokenExpireTime = ref(0) + + const setToken = (newToken: string, expireIn: number) => { + token.value = newToken + tokenExpireTime.value = Date.now() + expireIn * 1000 + secureStorage.setItem('auth_token', newToken) + secureStorage.setItem('token_expire_time', tokenExpireTime.value) + } + + const setRefreshToken = (newRefreshToken: string) => { + refreshToken.value = newRefreshToken + secureStorage.setItem('refresh_token', newRefreshToken) + } + + const isTokenExpired = (): boolean => { + return Date.now() >= tokenExpireTime.value + } + + const clearAuth = () => { + token.value = '' + refreshToken.value = '' + tokenExpireTime.value = 0 + secureStorage.removeItem('auth_token') + secureStorage.removeItem('refresh_token') + secureStorage.removeItem('token_expire_time') + } + + return { + token, + refreshToken, + tokenExpireTime, + setToken, + setRefreshToken, + isTokenExpired, + clearAuth + } +}) +``` + +#### 5.1.3 Token刷新 + +```typescript +// api/request.ts +import { useAuthStore } from '@/stores/auth' + +instance.interceptors.request.use(async (config) => { + const authStore = useAuthStore() + + if (authStore.isTokenExpired()) { + try { + const response = await instance.post('/auth/refresh', { + refreshToken: authStore.refreshToken + }) + authStore.setToken(response.token, response.expireIn) + authStore.setRefreshToken(response.refreshToken) + config.headers.Authorization = `Bearer ${response.token}` + } catch (error) { + authStore.clearAuth() + window.location.href = '/login' + return Promise.reject(error) + } + } else { + config.headers.Authorization = `Bearer ${authStore.token}` + } + + return config +}) +``` + +### 5.2 授权安全 + +#### 5.2.1 权限验证 + +```typescript +// utils/permission.ts +export interface Permission { + resource: string + action: string +} + +export function hasPermission(userPermissions: string[], required: Permission): boolean { + const permissionString = `${required.resource}:${required.action}` + return userPermissions.includes(permissionString) +} + +export function hasAnyPermission(userPermissions: string[], required: Permission[]): boolean { + return required.some(p => hasPermission(userPermissions, p)) +} + +export function hasAllPermissions(userPermissions: string[], required: Permission[]): boolean { + return required.every(p => hasPermission(userPermissions, p)) +} +``` + +#### 5.2.2 路由权限守卫 + +```typescript +// router/guards/permission.ts +import { useAuthStore } from '@/stores/auth' +import { usePermissionStore } from '@/stores/permission' + +router.beforeEach(async (to, from, next) => { + const authStore = useAuthStore() + const permissionStore = usePermissionStore() + + if (to.meta.requiresAuth && !authStore.token) { + next('/login') + return + } + + if (to.meta.permission) { + const required = to.meta.permission as Permission + if (!hasPermission(permissionStore.permissions, required)) { + next('/403') + return + } + } + + next() +}) +``` + +--- + +## 六、安全Headers + +### 6.1 基础安全Headers + +```typescript +// utils/headers.ts +export const securityHeaders = { + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'X-XSS-Protection': '1; mode=block', + 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Permissions-Policy': 'geolocation=(), microphone=(), camera=()' +} +``` + +### 6.2 动态设置Headers + +```typescript +// api/request.ts +instance.interceptors.request.use((config) => { + Object.assign(config.headers, securityHeaders) + return config +}) +``` + +--- + +## 七、点击劫持防护 + +### 7.1 X-Frame-Options + +```html + + +``` + +### 7.2 CSP frame-ancestors + +```html + + +``` + +### 7.3 JavaScript防护 + +```typescript +// utils/clickjacking.ts +export function preventClickjacking(): void { + if (window.self !== window.top) { + window.top.location = window.self.location + } +} + +// 在应用初始化时调用 +preventClickjacking() +``` + +--- + +## 八、安全键盘 + +### 8.1 安全键盘组件 + +```vue + + + + + + +``` + +--- + +## 九、安全日志与监控 + +### 9.1 操作日志 + +```typescript +// utils/logger.ts +interface LogEntry { + timestamp: number + level: 'info' | 'warn' | 'error' + action: string + userId?: number + details?: any +} + +export class SecurityLogger { + private logs: LogEntry[] = [] + + log(action: string, details?: any, level: 'info' | 'warn' | 'error' = 'info') { + const entry: LogEntry = { + timestamp: Date.now(), + level, + action, + userId: this.getUserId(), + details + } + + this.logs.push(entry) + this.sendToServer(entry) + } + + private getUserId(): number | undefined { + const authStore = useAuthStore() + return authStore.user?.id + } + + private async sendToServer(entry: LogEntry) { + try { + await api.post('/security/log', entry) + } catch (error) { + console.error('Failed to send security log:', error) + } + } +} + +export const securityLogger = new SecurityLogger() +``` + +### 9.2 异常监控 + +```typescript +// utils/sentry.ts +import * as Sentry from '@sentry/vue' + +export function setupSentry(app: App) { + Sentry.init({ + app, + dsn: import.meta.env.VITE_SENTRY_DSN, + environment: import.meta.env.MODE, + tracesSampleRate: 1.0, + beforeSend(event) { + if (event.request) { + delete event.request.cookies + delete event.request.headers + } + return event + } + }) +} + +// 全局错误处理 +window.addEventListener('error', (event) => { + securityLogger.log('window.error', { + message: event.message, + filename: event.filename, + lineno: event.lineno + }, 'error') +}) + +window.addEventListener('unhandledrejection', (event) => { + securityLogger.log('unhandledrejection', { + reason: event.reason + }, 'error') +}) +``` + +--- + +## 十、安全最佳实践 + +### 10.1 开发阶段 + +1. **代码审查** + - 所有代码必须经过安全审查 + - 使用ESLint安全规则 + - 使用npm audit检查依赖漏洞 + +2. **依赖管理** + - 定期更新依赖包 + - 使用npm audit fix修复漏洞 + - 使用Snyk等工具监控依赖安全 + +3. **环境变量** + - 敏感信息使用环境变量 + - 不要将密钥提交到代码仓库 + - 使用.env文件管理配置 + +### 10.2 测试阶段 + +1. **安全测试** + - 使用OWASP ZAP进行安全扫描 + - 进行渗透测试 + - 测试XSS、CSRF等漏洞 + +2. **代码扫描** + - 使用SonarQube进行代码质量检查 + - 使用ESLint进行代码规范检查 + - 使用Prettier进行代码格式化 + +### 10.3 部署阶段 + +1. **HTTPS强制** + - 使用SSL证书 + - 配置HSTS + - 禁用HTTP访问 + +2. **安全配置** + - 配置CSP策略 + - 配置安全Headers + - 配置防火墙规则 + +3. **监控告警** + - 配置错误监控 + - 配置性能监控 + - 配置安全告警 + +--- + +## 十一、合规性要求 + +### 11.1 GDPR合规 + +1. **数据最小化** + - 只收集必要的用户数据 + - 提供数据删除功能 + - 提供数据导出功能 + +2. **用户同意** + - 明确告知数据使用目的 + - 获取用户明确同意 + - 提供撤回同意的选项 + +3. **数据保护** + - 加密存储敏感数据 + - 限制数据访问权限 + - 定期进行安全审计 + +### 11.2 无障碍合规 + +1. **WCAG 2.1 AA级标准** + - 键盘导航支持 + - 屏幕阅读器支持 + - 颜色对比度符合标准 + +2. **ARIA标签** + - 为交互元素添加ARIA标签 + - 为动态内容添加ARIA标签 + - 为表单元素添加ARIA标签 + +--- + +## 十二、安全检查清单 + +### 12.1 代码提交前检查 + +- [ ] 所有用户输入都经过验证和过滤 +- [ ] 所有输出都经过转义 +- [ ] 敏感数据都经过加密存储 +- [ ] 敏感信息都经过脱敏显示 +- [ ] 所有API请求都包含CSRF Token +- [ ] 所有页面都配置了CSP策略 +- [ ] 所有页面都配置了安全Headers +- [ ] 所有密码都符合复杂度要求 +- [ ] 所有权限都经过验证 +- [ ] 所有操作都记录了日志 + +### 12.2 部署前检查 + +- [ ] 所有依赖包都是最新版本 +- [ ] 所有依赖包都没有已知漏洞 +- [ ] 所有环境变量都正确配置 +- [ ] 所有HTTPS证书都有效 +- [ ] 所有监控和告警都正常工作 +- [ ] 所有安全策略都正确配置 +- [ ] 所有安全测试都通过 +- [ ] 所有安全扫描都通过 +- [ ] 所有安全文档都完整 +- [ ] 所有安全培训都完成 + +--- + +## 十三、总结 + +本文档详细描述了健身房管理系统前端的安全规范,包括: + +1. **安全概述**:安全目标、安全原则、安全威胁 +2. **XSS防护**:输入验证、输出转义、CSP策略 +3. **CSRF防护**:Token验证、SameSite Cookie、双重Cookie提交 +4. **数据安全**:数据加密、数据脱敏、敏感信息存储 +5. **身份认证与授权**:认证安全、授权安全 +6. **安全Headers**:基础安全Headers、动态设置Headers +7. **点击劫持防护**:X-Frame-Options、CSP frame-ancestors +8. **安全键盘**:安全键盘组件 +9. **安全日志与监控**:操作日志、异常监控 +10. **安全最佳实践**:开发阶段、测试阶段、部署阶段 +11. **合规性要求**:GDPR合规、无障碍合规 +12. **安全检查清单**:代码提交前检查、部署前检查 + +通过遵循本文档的安全规范,可以确保健身房管理系统前端的安全性,符合金融级安全标准和监管要求。 diff --git a/docs/design/前端工程化建设文档.md b/docs/design/前端工程化建设文档.md new file mode 100644 index 0000000..a03be83 --- /dev/null +++ b/docs/design/前端工程化建设文档.md @@ -0,0 +1,928 @@ +# 健身房管理系统前端工程化建设文档 + +> 文档编号: GYM-FE-ENG-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-04 | 张翔 | 创建前端工程化建设文档 | + +--- + +## 参考文档 + +- 《健身房管理系统前端技术架构详细设计》 GYM-FE-ARCH-001 +- 《健身房管理系统前端开发规范》 GYM-FE-DEV-001 +- Vite 官方文档 +- GitHub Actions 文档 + +--- + +## 一、工程化概述 + +### 1.1 工程化目标 + +- **提高开发效率**:自动化重复性工作,减少手动操作 +- **保证代码质量**:通过自动化检查和测试,确保代码质量 +- **统一开发规范**:通过工具强制执行代码规范 +- **简化部署流程**:自动化构建和部署,减少人为错误 +- **提升团队协作**:统一开发环境和工具链 + +### 1.2 工程化体系 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 前端工程化体系 │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 开发工具链 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Node.js • npm/yarn • Git • VSCode │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 构建工具 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Vite • TypeScript • ESLint • Prettier │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 代码质量工具 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Husky • Commitlint • Lint-staged • Stylelint │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 测试工具 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • Vitest • Playwright • Coverage • Testing Library │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ CI/CD工具 │ │ +│ ├─────────────────────────────────────────────────────────────────┤ │ +│ │ • GitHub Actions • Docker • Nginx • CDN │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、构建工具配置 + +### 2.1 Vite配置 + +#### 2.1.1 基础配置 + +```typescript +// vite.config.ts +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()) + + return { + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + '@components': resolve(__dirname, 'src/components'), + '@utils': resolve(__dirname, 'src/utils'), + '@api': resolve(__dirname, 'src/api'), + '@stores': resolve(__dirname, 'src/stores') + } + }, + server: { + port: 5173, + host: true, + open: true, + proxy: { + '/api': { + target: env.VITE_API_BASE_URL, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + sourcemap: mode === 'development', + minify: 'terser', + terserOptions: { + compress: { + drop_console: mode === 'production', + drop_debugger: mode === 'production', + pure_funcs: mode === 'production' ? ['console.log', 'console.info'] : [] + } + }, + rollupOptions: { + output: { + manualChunks: { + 'vue-vendor': ['vue', 'vue-router', 'pinia'], + 'element-plus': ['element-plus'], + 'utils': ['lodash-es', 'dayjs'], + 'crypto': ['crypto-js', 'jsencrypt'] + } + } + }, + chunkSizeWarningLimit: 1000 + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/assets/styles/variables.scss";` + } + } + } + } +}) +``` + +#### 2.1.2 插件配置 + +```typescript +// vite.config.ts +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import Compression from 'vite-plugin-compression' +import { visualizer } from 'rollup-plugin-visualizer' + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + imports: ['vue', 'vue-router', 'pinia'], + dts: 'src/auto-imports.d.ts', + eslintrc: { + enabled: true + } + }), + Components({ + resolvers: [ElementPlusResolver()], + dts: 'src/components.d.ts' + }), + Compression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: 'gzip', + ext: '.gz' + }), + visualizer({ + open: false, + gzipSize: true, + brotliSize: true + }) + ] +}) +``` + +### 2.2 TypeScript配置 + +```json +// tsconfig.json +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@utils/*": ["src/utils/*"], + "@api/*": ["src/api/*"], + "@stores/*": ["src/stores/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +### 2.3 环境变量配置 + +```typescript +// .env.development +VITE_APP_TITLE=健身房管理系统(开发环境) +VITE_API_BASE_URL=http://localhost:8080/api +VITE_UPLOAD_URL=http://localhost:8080/upload +VITE_WS_URL=ws://localhost:8080/ws +VITE_SENTRY_DSN= +VITE_CRYPTO_SECRET_KEY=your-secret-key-here +VITE_RSA_PUBLIC_KEY=your-rsa-public-key-here + +// .env.production +VITE_APP_TITLE=健身房管理系统 +VITE_API_BASE_URL=https://api.example.com/api +VITE_UPLOAD_URL=https://api.example.com/upload +VITE_WS_URL=wss://api.example.com/ws +VITE_SENTRY_DSN=https://xxx@sentry.io/xxx +VITE_CRYPTO_SECRET_KEY=your-production-secret-key-here +VITE_RSA_PUBLIC_KEY=your-production-rsa-public-key-here + +// .env.staging +VITE_APP_TITLE=健身房管理系统(测试环境) +VITE_API_BASE_URL=https://staging-api.example.com/api +VITE_UPLOAD_URL=https://staging-api.example.com/upload +VITE_WS_URL=wss://staging-api.example.com/ws +VITE_SENTRY_DSN=https://xxx@sentry.io/xxx +VITE_CRYPTO_SECRET_KEY=your-staging-secret-key-here +VITE_RSA_PUBLIC_KEY=your-staging-rsa-public-key-here +``` + +--- + +## 三、代码规范工具 + +### 3.1 ESLint配置 + +```json +// .eslintrc.json +{ + "extends": [ + "plugin:vue/vue3-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:prettier/recommended" + ], + "parser": "vue-eslint-parser", + "parserOptions": { + "ecmaVersion": "latest", + "parser": "@typescript-eslint/parser", + "sourceType": "module" + }, + "plugins": ["vue", "@typescript-eslint", "prettier"], + "rules": { + "vue/multi-word-component-names": "off", + "vue/no-v-html": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "no-console": [ + "warn", + { + "allow": ["warn", "error"] + } + ], + "no-debugger": "error", + "prettier/prettier": "error" + } +} +``` + +### 3.2 Prettier配置 + +```json +// .prettierrc +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "es5", + "arrowParens": "avoid", + "endOfLine": "lf", + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "jsxSingleQuote": false, + "proseWrap": "preserve" +} +``` + +### 3.3 Stylelint配置 + +```json +// .stylelintrc.json +{ + "extends": ["stylelint-config-standard", "stylelint-config-prettier"], + "rules": { + "selector-class-pattern": "^[a-z][a-zA-Z0-9-__]*$", + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["deep", "global"] + } + ], + "selector-pseudo-element-no-unknown": [ + true, + { + "ignorePseudoElements": ["v-deep", "v-global", "v-slotted"] + } + ] + } +} +``` + +--- + +## 四、自动化工具 + +### 4.1 Husky配置 + +```json +// package.json +{ + "scripts": { + "prepare": "husky install", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "format": "prettier --write src/", + "lint:style": "stylelint \"src/**/*.{css,scss,vue}\" --fix" + } +} +``` + +```bash +# 初始化Husky +npx husky install + +# 添加pre-commit钩子 +npx husky add .husky/pre-commit "npx lint-staged" + +# 添加commit-msg钩子 +npx husky add .husky/commit-msg "npx commitlint --edit $1" +``` + +### 4.2 Lint-staged配置 + +```json +// .lintstagedrc.json +{ + "*.{js,jsx,ts,tsx,vue}": [ + "eslint --fix", + "prettier --write" + ], + "*.{css,scss,vue}": [ + "stylelint --fix" + ], + "*.{json,md}": [ + "prettier --write" + ] +} +``` + +### 4.3 Commitlint配置 + +```javascript +// commitlint.config.js +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'ci'] + ], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'scope-case': [2, 'always', 'lower-case'], + 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + 'header-max-length': [2, 'always', 100] + } +} +``` + +--- + +## 五、CI/CD流程 + +### 5.1 GitHub Actions配置 + +```yaml +# .github/workflows/ci.yml +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Run Prettier check + run: npm run format:check + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage/coverage-final.json + fail_ci_if_error: true + + build: + runs-on: ubuntu-latest + needs: [lint, test] + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/ +``` + +### 5.2 CD配置 + +```yaml +# .github/workflows/cd.yml +name: CD + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Deploy to server + uses: easingthemes/ssh-deploy@v3 + with: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + REMOTE_HOST: ${{ secrets.REMOTE_HOST }} + REMOTE_USER: ${{ secrets.REMOTE_USER }} + TARGET: /var/www/gym-manage/frontend + SOURCE: dist/ +``` + +--- + +## 六、项目脚手架 + +### 6.1 项目初始化 + +```bash +# 创建新项目 +npm create vite@latest gym-manage-frontend -- --template vue-ts + +# 进入项目目录 +cd gym-manage-frontend + +# 安装依赖 +npm install + +# 安装开发依赖 +npm install -D \ + @vitejs/plugin-vue \ + unplugin-auto-import \ + unplugin-vue-components \ + sass \ + eslint \ + @typescript-eslint/parser \ + @typescript-eslint/eslint-plugin \ + eslint-plugin-vue \ + prettier \ + eslint-config-prettier \ + eslint-plugin-prettier \ + husky \ + lint-staged \ + @commitlint/cli \ + @commitlint/config-conventional \ + vitest \ + @vue/test-utils \ + @playwright/test \ + rollup-plugin-visualizer \ + vite-plugin-compression + +# 安装生产依赖 +npm install \ + vue \ + vue-router \ + pinia \ + axios \ + dayjs \ + lodash-es \ + element-plus \ + dompurify \ + crypto-js \ + jsencrypt \ + web-vitals +``` + +### 6.2 目录结构初始化 + +```bash +# 创建目录结构 +mkdir -p src/{api,assets/{images,icons,styles},components/{base,business,layout},composables,config,directives,hooks,layouts,router,stores,types,utils,views} +mkdir -p src/test/{unit,e2e} +mkdir -p public + +# 创建配置文件 +touch .env.development .env.production .env.staging +touch .eslintrc.json .prettierrc .stylelintrc.json +touch tsconfig.json tsconfig.node.json +``` + +### 6.3 基础文件创建 + +```typescript +// src/main.ts +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import App from './App.vue' +import router from './router' +import './assets/styles/main.scss' + +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia) +app.use(router) +app.use(ElementPlus) + +app.mount('#app') +``` + +```typescript +// src/App.vue + + + + + +``` + +--- + +## 七、开发工具链 + +### 7.1 VSCode配置 + +```json +// .vscode/settings.json +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.fixAll.stylelint": true + }, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue" + ], + "typescript.tsdk": "node_modules/typescript/lib", + "volar.takeOverMode.enabled": true, + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} +``` + +```json +// .vscode/extensions.json +{ + "recommendations": [ + "vue.volar", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "bradlc.vscode-tailwindcss", + "eamodio.gitlens" + ] +} +``` + +### 7.2 Git配置 + +```bash +# .gitignore +node_modules +dist +dist-ssr +*.local +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.DS_Store +*.log +coverage +.nyc_output +.env.local +.env.*.local +``` + +### 7.3 NPM脚本 + +```json +// package.json +{ + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "build:staging": "vue-tsc && vite build --mode staging", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "lint:style": "stylelint \"src/**/*.{css,scss,vue}\" --fix", + "format": "prettier --write src/", + "format:check": "prettier --check src/", + "test": "vitest", + "test:unit": "vitest run", + "test:coverage": "vitest run --coverage", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "type-check": "vue-tsc --noEmit", + "prepare": "husky install" + } +} +``` + +--- + +## 八、最佳实践 + +### 8.1 依赖管理 + +#### 8.1.1 依赖版本管理 + +```json +// package.json +{ + "dependencies": { + "vue": "^3.4.0", + "vue-router": "^4.2.0", + "pinia": "^2.1.0" + }, + "devDependencies": { + "vite": "^5.0.0", + "typescript": "^5.0.0", + "eslint": "^8.56.0" + } +} +``` + +#### 8.1.2 依赖安全检查 + +```bash +# 检查依赖漏洞 +npm audit + +# 自动修复依赖漏洞 +npm audit fix + +# 强制修复依赖漏洞 +npm audit fix --force +``` + +### 8.2 性能监控 + +#### 8.2.1 构建分析 + +```bash +# 生成构建分析报告 +npm run build + +# 查看分析报告 +open stats.html +``` + +#### 8.2.2 Bundle大小优化 + +```typescript +// vite.config.ts +export default defineConfig({ + build: { + rollupOptions: { + output: { + manualChunks: { + 'vue-vendor': ['vue', 'vue-router', 'pinia'], + 'element-plus': ['element-plus'], + 'utils': ['lodash-es', 'dayjs'] + } + } + }, + chunkSizeWarningLimit: 500 + } +}) +``` + +### 8.3 文档管理 + +#### 8.3.1 README文档 + +```markdown +# 健身房管理系统前端 + +## 项目介绍 + +健身房管理系统前端项目,基于Vue3 + Vite + TypeScript构建。 + +## 技术栈 + +- Vue 3.4+ +- TypeScript 5.0+ +- Vite 5.0+ +- Pinia 2.1+ +- Element Plus 2.5+ + +## 快速开始 + +### 安装依赖 + +\`\`\`bash +npm install +\`\`\` + +### 开发 + +\`\`\`bash +npm run dev +\`\`\` + +### 构建 + +\`\`\`bash +npm run build +\`\`\` + +### 测试 + +\`\`\`bash +npm run test +\`\`\` + +## 项目结构 + +\`\`\` +src/ +├── api/ # API接口 +├── assets/ # 静态资源 +├── components/ # 组件 +├── composables/ # Composables +├── config/ # 配置 +├── router/ # 路由 +├── stores/ # 状态管理 +├── types/ # 类型定义 +├── utils/ # 工具函数 +└── views/ # 页面 +\`\`\` + +## 开发规范 + +详见 [前端开发规范](./docs/design/前端开发规范.md) + +## 许可证 + +MIT +``` + +#### 8.3.2 CHANGELOG文档 + +```markdown +# Changelog + +## [1.0.0] - 2026-03-04 + +### Added +- 会员管理功能 +- 课程预约功能 +- 扫码签到功能 +- 数据统计功能 + +### Changed +- 升级Vue到3.4版本 +- 优化构建配置 + +### Fixed +- 修复预约时间冲突问题 +- 修复签到记录显示问题 + +### Security +- 添加XSS防护 +- 添加CSRF防护 +``` + +--- + +## 九、总结 + +本文档详细描述了健身房管理系统前端的工程化建设,包括: + +1. **工程化概述**:工程化目标、工程化体系 +2. **构建工具配置**:Vite配置、TypeScript配置、环境变量配置 +3. **代码规范工具**:ESLint配置、Prettier配置、Stylelint配置 +4. **自动化工具**:Husky配置、Lint-staged配置、Commitlint配置 +5. **CI/CD流程**:GitHub Actions配置、CD配置 +6. **项目脚手架**:项目初始化、目录结构初始化、基础文件创建 +7. **开发工具链**:VSCode配置、Git配置、NPM脚本 +8. **最佳实践**:依赖管理、性能监控、文档管理 + +通过遵循本文档的工程化建设指南,可以建立完善的前端工程化体系,提高开发效率、保证代码质量、简化部署流程。 diff --git a/docs/design/前端开发规范.md b/docs/design/前端开发规范.md new file mode 100644 index 0000000..533c878 --- /dev/null +++ b/docs/design/前端开发规范.md @@ -0,0 +1,1017 @@ +# 健身房管理系统前端开发规范文档 + +> 文档编号: GYM-FE-DEV-001 +> 版本: v1.0 +> 日期: 2026-03-04 +> 作者: 张翔 +> 状态: 初稿 + +--- + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-04 | 张翔 | 创建前端开发规范 | + +--- + +## 参考文档 + +- 《健身房管理系统前端技术架构详细设计》 GYM-FE-ARCH-001 +- Vue 3 风格指南 +- TypeScript 编码规范 +- Airbnb JavaScript Style Guide + +--- + +## 一、编码规范 + +### 1.1 代码风格 + +#### 1.1.1 使用ESLint + +```json +// .eslintrc.json +{ + "extends": [ + "plugin:vue/vue3-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "rules": { + "vue/multi-word-component-names": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} +``` + +#### 1.1.2 使用Prettier + +```json +// .prettierrc +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "es5", + "arrowParens": "avoid", + "endOfLine": "lf" +} +``` + +### 1.2 命名规范 + +#### 1.2.1 文件命名 + +| 类型 | 命名规范 | 示例 | +|------|---------|------| +| **组件文件** | PascalCase | MemberList.vue, CourseCard.vue | +| **工具文件** | camelCase | formatDate.ts, validator.ts | +| **类型文件** | camelCase | api.ts, models.ts | +| **常量文件** | UPPER_SNAKE_CASE | API_BASE_URL.ts | +| **样式文件** | kebab-case | member-list.scss, button.css | + +#### 1.2.2 变量命名 + +```typescript +// 布尔值:使用is/has/can前缀 +const isActive = true +const hasPermission = false +const canEdit = true + +// 常量:使用UPPER_SNAKE_CASE +const API_BASE_URL = 'https://api.example.com' +const MAX_RETRY_COUNT = 3 + +// 函数:使用camelCase,动词开头 +function getMemberList() {} +function validatePhoneNumber() {} +function handleSearch() {} + +// 类:使用PascalCase +class UserService {} +class MemberValidator {} + +// 接口:使用PascalCase,I前缀可选 +interface Member {} +interface IMember {} + +// 类型别名:使用PascalCase +type MemberStatus = 'active' | 'inactive' + +// 枚举:使用PascalCase +enum MemberLevel { + BRONZE = 1, + SILVER = 2, + GOLD = 3 +} +``` + +#### 1.2.3 组件命名 + +```vue + + + + +``` + +### 1.3 代码格式 + +#### 1.3.1 缩进与空格 + +```typescript +// 使用2个空格缩进 +function example() { + if (condition) { + doSomething() + } +} + +// 对象和数组使用空格 +const obj = { a: 1, b: 2 } +const arr = [1, 2, 3] + +// 运算符前后使用空格 +const sum = a + b +const result = a > b ? a : b +``` + +#### 1.3.2 引号使用 + +```typescript +// 优先使用单引号 +const name = 'John' +const message = 'Hello, world!' + +// 字符串中包含单引号时使用双引号 +const quote = "It's a beautiful day" + +// 模板字符串使用反引号 +const greeting = `Hello, ${name}!` +``` + +#### 1.3.3 分号使用 + +```typescript +// 不使用分号 +const a = 1 +const b = 2 +function example() { + return a + b +} +``` + +--- + +## 二、Vue组件规范 + +### 2.1 组件结构 + +```vue + + + + + +``` + +### 2.2 Props定义 + +```typescript +// 使用TypeScript接口定义Props +interface Props { + title: string + count?: number + items: string[] + config: { + enabled: boolean + timeout: number + } +} + +// 使用withDefaults设置默认值 +const props = withDefaults(defineProps(), { + count: 0, + items: () => [], + config: () => ({ enabled: true, timeout: 5000 }) +}) +``` + +### 2.3 Emits定义 + +```typescript +// 使用TypeScript定义Emits +const emit = defineEmits<{ + click: [event: MouseEvent] + change: [value: string] + submit: [data: FormData] + cancel: [] +}>() + +// 触发事件 +emit('click', event) +emit('change', newValue) +emit('submit', formData) +emit('cancel') +``` + +### 2.4 响应式数据 + +```typescript +// 使用ref定义基本类型 +const count = ref(0) +const message = ref('Hello') + +// 使用reactive定义对象 +const user = reactive({ + name: 'John', + age: 30 +}) + +// 使用computed定义计算属性 +const fullName = computed(() => `${user.name} is ${user.age} years old`) + +// 使用readonly定义只读数据 +const readonlyData = readonly({ id: 1, name: 'John' }) + +// 使用shallowRef和shallowReactive优化性能 +const largeList = shallowRef([]) +const largeObject = shallowReactive({}) +``` + +### 2.5 组件通信 + +#### 2.5.1 Props传递 + +```vue + + + + +``` + +#### 2.5.2 事件传递 + +```vue + + + + +``` + +#### 2.5.3 Provide/Inject + +```typescript +// 父组件 +import { provide } from 'vue' + +const theme = ref('light') +provide('theme', theme) + +// 子组件 +import { inject } from 'vue' + +const theme = inject>('theme') +``` + +--- + +## 三、TypeScript规范 + +### 3.1 类型定义 + +#### 3.1.1 基础类型 + +```typescript +// 使用明确的类型 +const count: number = 10 +const name: string = 'John' +const isActive: boolean = true +const items: string[] = ['a', 'b', 'c'] +const user: { name: string; age: number } = { name: 'John', age: 30 } +``` + +#### 3.1.2 接口定义 + +```typescript +// 定义接口 +interface Member { + id: number + name: string + phone: string + level: MemberLevel + status: MemberStatus +} + +// 可选属性 +interface User { + id: number + name: string + email?: string +} + +// 只读属性 +interface Config { + readonly id: number + name: string +} + +// 索引签名 +interface StringDictionary { + [key: string]: string +} +``` + +#### 3.1.3 类型别名 + +```typescript +// 定义类型别名 +type MemberStatus = 'active' | 'inactive' | 'frozen' +type MemberLevel = 1 | 2 | 3 | 4 | 5 + +// 联合类型 +type ID = number | string + +// 交叉类型 +type Name = { firstName: string; lastName: string } +type Age = { age: number } +type Person = Name & Age +``` + +#### 3.1.4 泛型 + +```typescript +// 泛型函数 +function identity(arg: T): T { + return arg +} + +// 泛型接口 +interface Response { + code: number + data: T + message: string +} + +// 泛型类 +class Storage { + private items: T[] = [] + + add(item: T): void { + this.items.push(item) + } + + get(index: number): T | undefined { + return this.items[index] + } +} +``` + +### 3.2 类型断言 + +```typescript +// 使用as关键字进行类型断言 +const element = document.getElementById('app') as HTMLElement + +// 使用尖括号语法(JSX中不可用) +const element2 = document.getElementById('app') + +// 非空断言 +const value = input.value! +``` + +### 3.3 类型守卫 + +```typescript +// typeof类型守卫 +function processValue(value: string | number) { + if (typeof value === 'string') { + console.log(value.toUpperCase()) + } else { + console.log(value.toFixed(2)) + } +} + +// instanceof类型守卫 +class Dog { + bark() {} +} + +class Cat { + meow() {} +} + +function makeSound(animal: Dog | Cat) { + if (animal instanceof Dog) { + animal.bark() + } else { + animal.meow() + } +} + +// 自定义类型守卫 +interface Member { + id: number + name: string + type: 'individual' | 'corporate' +} + +function isCorporateMember(member: Member): member is Member & { type: 'corporate' } { + return member.type === 'corporate' +} +``` + +--- + +## 四、注释规范 + +### 4.1 文件注释 + +```typescript +/** + * 会员服务 + * + * 提供会员相关的业务逻辑处理 + * + * @example + * const memberService = new MemberService() + * const members = await memberService.getList() + */ +export class MemberService { + // ... +} +``` + +### 4.2 函数注释 + +```typescript +/** + * 获取会员列表 + * + * @param params - 查询参数 + * @param params.page - 页码 + * @param params.pageSize - 每页数量 + * @param params.keyword - 搜索关键词 + * @returns 会员列表数据 + * @throws {Error} 当API请求失败时抛出错误 + * + * @example + * const result = await getMemberList({ page: 1, pageSize: 10 }) + */ +export async function getMemberList(params: { + page: number + pageSize: number + keyword?: string +}): Promise { + // ... +} +``` + +### 4.3 类注释 + +```typescript +/** + * 会员验证器 + * + * 提供会员数据验证功能 + */ +export class MemberValidator { + /** + * 验证手机号 + * + * @param phone - 手机号 + * @returns 验证结果 + */ + validatePhone(phone: string): ValidationResult { + // ... + } +} +``` + +### 4.4 行内注释 + +```typescript +// 计算会员等级 +const level = calculateLevel(exp) + +// TODO: 需要优化这个算法的性能 +const result = complexCalculation(data) + +// FIXME: 这里有个bug,需要修复 +const value = buggyFunction() + +// HACK: 临时解决方案,后续需要重构 +const temp = workaround(data) +``` + +--- + +## 五、文件组织规范 + +### 5.1 目录结构 + +``` +src/ +├── api/ # API接口 +│ ├── modules/ # API模块 +│ │ ├── auth.ts +│ │ ├── member.ts +│ │ └── booking.ts +│ └── request.ts # Axios封装 +├── assets/ # 静态资源 +│ ├── images/ # 图片 +│ ├── icons/ # 图标 +│ └── styles/ # 样式 +├── components/ # 组件 +│ ├── base/ # 基础组件 +│ ├── business/ # 业务组件 +│ └── layout/ # 布局组件 +├── composables/ # Composables +├── config/ # 配置 +├── directives/ # 自定义指令 +├── hooks/ # Hooks +├── layouts/ # 布局 +├── router/ # 路由 +├── stores/ # 状态管理 +├── types/ # 类型定义 +├── utils/ # 工具函数 +├── views/ # 页面 +├── App.vue +└── main.ts +``` + +### 5.2 文件导入顺序 + +```typescript +// 1. Vue相关导入 +import { ref, computed, onMounted } from 'vue' +import { useRouter } from 'vue-router' + +// 2. 第三方库导入 +import axios from 'axios' +import dayjs from 'dayjs' + +// 3. 内部模块导入 +import { useAuthStore } from '@/stores/auth' +import { formatDate } from '@/utils/formatter' + +// 4. 类型导入 +import type { Member, MemberListParams } from '@/types/models' + +// 5. 样式导入 +import './styles/index.scss' +``` + +### 5.3 导出规范 + +```typescript +// 命名导出 +export function formatDate(date: Date): string {} +export function formatTime(time: Date): string {} + +// 默认导出(仅用于组件) +export default defineComponent({ + // ... +}) + +// 类型导出 +export type { Member, MemberStatus } +export interface { MemberListParams } +``` + +--- + +## 六、Git提交规范 + +### 6.1 提交信息格式 + +``` +(): + + + +