diff --git a/gym-manage-api/docs/datacount-api.md b/gym-manage-api/docs/datacount-api.md
new file mode 100644
index 0000000..1dc7e02
--- /dev/null
+++ b/gym-manage-api/docs/datacount-api.md
@@ -0,0 +1,848 @@
+# 数据统计模块 API 文档
+
+> 模块:gym-dataCount
+> 版本:1.0.0
+> 更新日期:2026-06-06
+
+## 概述
+
+数据统计模块提供会员数据统计、预约数据统计和数据导出功能,支持按日期查询、日期范围查询和汇总统计。
+
+---
+
+## 基础信息
+
+- **Base URL**: `/api/stats`
+- **Content-Type**: `application/json`
+- **响应格式**: JSON
+
+---
+
+## 1. 会员数据统计
+
+### 1.1 获取指定日期的会员统计
+
+获取某一天的会员数据统计信息。
+
+**请求**
+
+```http
+GET /api/stats/member/date/{date}
+```
+
+**路径参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| date | string | 是 | 统计日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/date/2026-06-01"
+```
+
+**响应示例**
+
+```json
+{
+ "date": "2026-06-01",
+ "newMembers": 5,
+ "activeMembers": 0,
+ "retainedMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+}
+```
+
+**响应字段说明**
+
+| 字段 | 类型 | 说明 |
+|-----|------|------|
+| date | string | 统计日期 |
+| newMembers | integer | 当日新增会员数 |
+| activeMembers | integer | 当日活跃会员数(有会员卡操作的会员) |
+| retainedMembers | integer | 留存会员数 |
+| totalMembers | integer | 总会员数(累计) |
+| maleMembers | integer | 男性会员总数 |
+| femaleMembers | integer | 女性会员总数 |
+| cardPurchaseCount | integer | 当日购卡人数 |
+| averageAge | number | 会员平均年龄 |
+
+---
+
+### 1.2 获取日期范围内的会员统计列表
+
+获取指定日期范围内每天的会员统计数据。
+
+**请求**
+
+```http
+GET /api/stats/member/range?startDate=2026-06-01&endDate=2026-06-05
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/range?startDate=2026-06-01&endDate=2026-06-03"
+```
+
+**响应示例**
+
+```json
+[
+ {
+ "date": "2026-06-01",
+ "newMembers": 5,
+ "activeMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+ },
+ {
+ "date": "2026-06-02",
+ "newMembers": 4,
+ "activeMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+ },
+ {
+ "date": "2026-06-03",
+ "newMembers": 3,
+ "activeMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+ }
+]
+```
+
+---
+
+### 1.3 获取会员统计汇总
+
+获取指定日期范围内的会员数据汇总统计。
+
+**请求**
+
+```http
+GET /api/stats/member/summary?startDate=2026-06-01&endDate=2026-06-30
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/summary?startDate=2026-06-01&endDate=2026-06-06"
+```
+
+**响应示例**
+
+```json
+{
+ "startDate": "2026-06-01",
+ "endDate": "2026-06-06",
+ "newMembers": 19,
+ "activeMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+}
+```
+
+---
+
+## 2. 预约数据统计
+
+### 2.1 获取指定日期的预约统计
+
+获取某一天的团课预约数据统计信息。
+
+**请求**
+
+```http
+GET /api/stats/booking/date/{date}
+```
+
+**路径参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| date | string | 是 | 统计日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/date/2026-06-01"
+```
+
+**响应示例**
+
+```json
+{
+ "date": "2026-06-01",
+ "totalBookings": 32,
+ "cancelledBookings": 5,
+ "attendedBookings": 27,
+ "bookingSuccessRate": 0.0,
+ "cancellationRate": 15.63,
+ "attendanceRate": 84.38,
+ "popularCourseName": "燃脂单车",
+ "popularCourseCount": 19,
+ "totalCourses": 3,
+ "fullCourses": 1
+}
+```
+
+**响应字段说明**
+
+| 字段 | 类型 | 说明 |
+|-----|------|------|
+| date | string | 统计日期 |
+| totalBookings | integer | 预约总数 |
+| cancelledBookings | integer | 取消预约数 |
+| attendedBookings | integer | 实际到场数 |
+| bookingSuccessRate | number | 预约成功率(保留) |
+| cancellationRate | number | 取消率(百分比) |
+| attendanceRate | number | 到场率(百分比) |
+| popularCourseName | string | 最热门课程名称 |
+| popularCourseCount | integer | 热门课程预约数 |
+| totalCourses | integer | 当日团课总数 |
+| fullCourses | integer | 满员课程数 |
+
+---
+
+### 2.2 获取日期范围内的预约统计列表
+
+获取指定日期范围内每天的预约统计数据。
+
+**请求**
+
+```http
+GET /api/stats/booking/range?startDate=2026-06-01&endDate=2026-06-05
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/range?startDate=2026-06-01&endDate=2026-06-03"
+```
+
+**响应示例**
+
+```json
+[
+ {
+ "date": "2026-06-01",
+ "totalBookings": 32,
+ "cancelledBookings": 5,
+ "attendedBookings": 27,
+ "cancellationRate": 15.63,
+ "attendanceRate": 84.38,
+ "popularCourseName": "燃脂单车",
+ "popularCourseCount": 19,
+ "totalCourses": 3,
+ "fullCourses": 1
+ },
+ {
+ "date": "2026-06-02",
+ "totalBookings": 30,
+ "cancelledBookings": 3,
+ "attendedBookings": 27,
+ "cancellationRate": 10.00,
+ "attendanceRate": 90.00,
+ "popularCourseName": "流瑜伽",
+ "popularCourseCount": 10,
+ "totalCourses": 3,
+ "fullCourses": 0
+ },
+ {
+ "date": "2026-06-03",
+ "totalBookings": 35,
+ "cancelledBookings": 4,
+ "attendedBookings": 31,
+ "cancellationRate": 11.43,
+ "attendanceRate": 88.57,
+ "popularCourseName": "哈他瑜伽",
+ "popularCourseCount": 12,
+ "totalCourses": 3,
+ "fullCourses": 2
+ }
+]
+```
+
+---
+
+### 2.3 获取预约统计汇总
+
+获取指定日期范围内的预约数据汇总统计。
+
+**请求**
+
+```http
+GET /api/stats/booking/summary?startDate=2026-06-01&endDate=2026-06-30
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/summary?startDate=2026-06-01&endDate=2026-06-06"
+```
+
+**响应示例**
+
+```json
+{
+ "startDate": "2026-06-01",
+ "endDate": "2026-06-06",
+ "totalBookings": 172,
+ "cancelledBookings": 16,
+ "attendedBookings": 156,
+ "cancellationRate": 9.30,
+ "attendanceRate": 90.70
+}
+```
+
+---
+
+## 3. 数据导出
+
+### 3.1 导出会员统计数据
+
+导出指定日期范围内的会员统计数据为CSV文件。
+
+**请求**
+
+```http
+GET /api/stats/export/member?startDate=2026-06-01&endDate=2026-06-30
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/member?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output member_stats.csv
+```
+
+**响应**
+
+- **Content-Type**: `text/csv; charset=UTF-8`
+- **Content-Disposition**: `attachment; filename=member_statistics_2026-06-01_2026-06-03.csv`
+
+**CSV格式示例**
+
+```csv
+统计日期,新增会员数,活跃会员数,总会员数,男性会员数,女性会员数,购卡人数,平均年龄
+2026-06-01,5,0,19,10,9,19,29.5
+2026-06-02,4,0,19,10,9,19,29.5
+2026-06-03,3,0,19,10,9,19,29.5
+```
+
+---
+
+### 3.2 导出预约统计数据
+
+导出指定日期范围内的预约统计数据为CSV文件。
+
+**请求**
+
+```http
+GET /api/stats/export/booking?startDate=2026-06-01&endDate=2026-06-30
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/booking?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output booking_stats.csv
+```
+
+**响应**
+
+- **Content-Type**: `text/csv; charset=UTF-8`
+- **Content-Disposition**: `attachment; filename=booking_statistics_2026-06-01_2026-06-03.csv`
+
+**CSV格式示例**
+
+```csv
+统计日期,预约总数,取消预约数,实际到场数,取消率(%),到场率(%),热门课程,热门课程预约数,团课总数,满员课程数
+2026-06-01,32,5,27,15.63,84.38,燃脂单车,19,3,1
+2026-06-02,30,3,27,10.00,90.00,流瑜伽,10,3,0
+2026-06-03,35,4,31,11.43,88.57,哈他瑜伽,12,3,2
+```
+
+---
+
+### 3.3 导出全部统计数据
+
+导出指定日期范围内的会员和预约统计数据为CSV文件。
+
+**请求**
+
+```http
+GET /api/stats/export/all?startDate=2026-06-01&endDate=2026-06-30
+```
+
+**查询参数**
+
+| 参数 | 类型 | 必填 | 说明 |
+|-----|------|-----|------|
+| startDate | string | 是 | 开始日期,格式:yyyy-MM-dd |
+| endDate | string | 是 | 结束日期,格式:yyyy-MM-dd |
+
+**请求示例**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/all?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output all_stats.csv
+```
+
+**响应**
+
+- **Content-Type**: `text/csv; charset=UTF-8`
+- **Content-Disposition**: `attachment; filename=all_statistics_2026-06-01_2026-06-03.csv`
+
+**CSV格式示例**
+
+```csv
+=== 会员数据统计 ===
+统计日期,新增会员数,活跃会员数,总会员数,男性会员数,女性会员数,购卡人数,平均年龄
+2026-06-01,5,0,19,10,9,19,29.5
+2026-06-02,4,0,19,10,9,19,29.5
+2026-06-03,3,0,19,10,9,19,29.5
+
+=== 预约数据统计 ===
+统计日期,预约总数,取消预约数,实际到场数,取消率(%),到场率(%),热门课程,热门课程预约数,团课总数,满员课程数
+2026-06-01,32,5,27,15.63,84.38,燃脂单车,19,3,1
+2026-06-02,30,3,27,10.00,90.00,流瑜伽,10,3,0
+2026-06-03,35,4,31,11.43,88.57,哈他瑜伽,12,3,2
+```
+
+---
+
+## 4. 错误响应
+
+所有接口在发生错误时返回统一的错误格式:
+
+```json
+{
+ "success": false,
+ "message": "错误描述信息"
+}
+```
+
+**常见错误码**
+
+| HTTP状态码 | 说明 |
+|-----------|------|
+| 400 Bad Request | 参数格式错误或缺少必填参数 |
+| 404 Not Found | 指定日期的数据不存在 |
+| 500 Internal Server Error | 服务器内部错误 |
+
+---
+
+## 5. 定时任务
+
+模块内置以下定时任务,自动计算统计数据并缓存到Redis:
+
+| 任务名称 | 执行时间 | 说明 |
+|---------|---------|------|
+| 每日统计 | 每日凌晨1点 | 计算前一天的会员和预约统计 |
+| 每周汇总 | 每周一凌晨2点 | 计算上周的汇总统计 |
+| 每月汇总 | 每月1号凌晨3点 | 计算上月的汇总统计 |
+
+**缓存策略**
+
+- 缓存键前缀:`stats:member:` 和 `stats:booking:`
+- 缓存过期时间:3600秒(1小时)
+- 每日定时任务会自动刷新缓存
+
+---
+
+## 6. 数据来源
+
+统计数据来源于以下数据表:
+
+| 统计类型 | 数据表 | 说明 |
+|---------|-------|------|
+| 会员统计 | `member_user` | 会员基本信息表 |
+| 会员卡统计 | `member_card_record` | 会员卡记录表 |
+| 预约统计 | `group_course_booking` | 团课预约记录表 |
+| 课程统计 | `group_course` | 团课课程表 |
+
+---
+
+## 7. 技术实现
+
+- **框架**: Spring WebFlux(响应式编程)
+- **数据库访问**: Spring Data R2DBC
+- **缓存**: Redis(ReactiveRedisTemplate)
+- **API文档**: OpenAPI 3.0 / Swagger
+- **导出格式**: CSV
+
+---
+
+## 附录:接口列表汇总
+
+| 方法 | 路径 | 说明 |
+|-----|------|------|
+| GET | `/api/stats/member/date/{date}` | 获取指定日期会员统计 |
+| GET | `/api/stats/member/range` | 获取日期范围会员统计列表 |
+| GET | `/api/stats/member/summary` | 获取会员统计汇总 |
+| GET | `/api/stats/booking/date/{date}` | 获取指定日期预约统计 |
+| GET | `/api/stats/booking/range` | 获取日期范围预约统计列表 |
+| GET | `/api/stats/booking/summary` | 获取预约统计汇总 |
+| GET | `/api/stats/export/member` | 导出会员统计CSV |
+| GET | `/api/stats/export/booking` | 导出预约统计CSV |
+| GET | `/api/stats/export/all` | 导出全部统计CSV |
+
+---
+
+## 8. 测试指南
+
+### 8.1 测试数据说明
+
+数据库已预置测试数据,日期范围为 **2026-06-01 至 2026-06-10**。
+
+#### 会员数据分布
+
+| 日期 | 新增会员数 | 男性会员 | 女性会员 |
+|------|-----------|---------|---------|
+| 2026-06-01 | 5 | 3 | 2 |
+| 2026-06-02 | 4 | 2 | 2 |
+| 2026-06-03 | 3 | 1 | 2 |
+| 2026-06-04 | 2 | 1 | 1 |
+| 2026-06-05 | 3 | 2 | 1 |
+| 2026-06-06 | 2 | 1 | 1 |
+| **合计** | **19** | **10** | **9** |
+
+#### 预约数据分布
+
+| 日期 | 预约总数 | 取消预约 | 实际到场 | 满员课程数 |
+|------|---------|---------|---------|-----------|
+| 2026-06-01 | 32 | 5 | 27 | 1 |
+| 2026-06-02 | 30 | 3 | 27 | 0 |
+| 2026-06-03 | 35 | 4 | 31 | 2 |
+| 2026-06-04 | 28 | 2 | 26 | 1 |
+| 2026-06-05 | 25 | 1 | 24 | 0 |
+| 2026-06-06 | 22 | 1 | 21 | 0 |
+
+---
+
+### 8.2 测试用例
+
+#### 8.2.1 会员数据统计
+
+**测试1:获取单日会员统计**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/date/2026-06-01"
+```
+
+**预期结果**:
+```json
+{
+ "date": "2026-06-01",
+ "newMembers": 5,
+ "activeMembers": 0,
+ "retainedMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+}
+```
+
+---
+
+**测试2:获取日期范围会员统计**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/range?startDate=2026-06-01&endDate=2026-06-03"
+```
+
+**预期结果**:返回3条记录,分别为 2026-06-01、2026-06-02、2026-06-03 的统计数据。
+
+---
+
+**测试3:获取会员统计汇总**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/summary?startDate=2026-06-01&endDate=2026-06-06"
+```
+
+**预期结果**:
+```json
+{
+ "startDate": "2026-06-01",
+ "endDate": "2026-06-06",
+ "newMembers": 19,
+ "activeMembers": 0,
+ "totalMembers": 19,
+ "maleMembers": 10,
+ "femaleMembers": 9,
+ "cardPurchaseCount": 19,
+ "averageAge": 29.5
+}
+```
+
+---
+
+#### 8.2.2 预约数据统计
+
+**测试4:获取单日预约统计**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/date/2026-06-01"
+```
+
+**预期结果**:
+```json
+{
+ "date": "2026-06-01",
+ "totalBookings": 32,
+ "cancelledBookings": 5,
+ "attendedBookings": 27,
+ "bookingSuccessRate": 0.0,
+ "cancellationRate": 15.63,
+ "attendanceRate": 84.38,
+ "popularCourseName": "燃脂单车",
+ "popularCourseCount": 19,
+ "totalCourses": 3,
+ "fullCourses": 1
+}
+```
+
+---
+
+**测试5:获取日期范围预约统计**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/range?startDate=2026-06-01&endDate=2026-06-03"
+```
+
+**预期结果**:返回3条记录,分别为 2026-06-01、2026-06-02、2026-06-03 的统计数据。
+
+---
+
+**测试6:获取预约统计汇总**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/booking/summary?startDate=2026-06-01&endDate=2026-06-06"
+```
+
+**预期结果**:
+```json
+{
+ "startDate": "2026-06-01",
+ "endDate": "2026-06-06",
+ "totalBookings": 172,
+ "cancelledBookings": 16,
+ "attendedBookings": 156,
+ "cancellationRate": 9.30,
+ "attendanceRate": 90.70
+}
+```
+
+---
+
+#### 8.2.3 数据导出
+
+**测试7:导出会员统计数据**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/member?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output member_stats.csv
+```
+
+**预期结果**:下载 CSV 文件,包含 2026-06-01 至 2026-06-03 的会员统计数据。
+
+---
+
+**测试8:导出预约统计数据**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/booking?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output booking_stats.csv
+```
+
+**预期结果**:下载 CSV 文件,包含 2026-06-01 至 2026-06-03 的预约统计数据。
+
+---
+
+**测试9:导出全部统计数据**
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/export/all?startDate=2026-06-01&endDate=2026-06-03" \
+ -H "Accept: text/csv" \
+ --output all_stats.csv
+```
+
+**预期结果**:下载 CSV 文件,包含会员和预约的完整统计数据。
+
+---
+
+### 8.3 测试参数说明
+
+#### 日期参数格式
+
+所有日期参数必须使用 `yyyy-MM-dd` 格式,例如:
+- ✅ `2026-06-01`
+- ❌ `2026/06/01`
+- ❌ `2026-6-1`
+- ❌ `06-01-2026`
+
+#### 日期范围参数
+
+| 参数 | 说明 | 示例 |
+|-----|------|------|
+| startDate | 开始日期(包含) | `2026-06-01` |
+| endDate | 结束日期(包含) | `2026-06-06` |
+
+**注意事项**:
+- `startDate` 必须 ≤ `endDate`
+- 日期范围不能超过 365 天
+- 日期必须在测试数据范围内(2026-06-01 至 2026-06-10)
+
+---
+
+### 8.4 常见测试场景
+
+#### 场景1:测试单日统计
+
+```bash
+# 测试6月1日的会员和预约统计
+curl "http://localhost:8080/api/stats/member/date/2026-06-01"
+curl "http://localhost:8080/api/stats/booking/date/2026-06-01"
+```
+
+---
+
+#### 场景2:测试一周统计
+
+```bash
+# 测试6月第一周的统计
+curl "http://localhost:8080/api/stats/member/summary?startDate=2026-06-01&endDate=2026-06-06"
+curl "http://localhost:8080/api/stats/booking/summary?startDate=2026-06-01&endDate=2026-06-06"
+```
+
+---
+
+#### 场景3:测试数据导出
+
+```bash
+# 导出6月前3天的所有统计数据
+curl "http://localhost:8080/api/stats/export/all?startDate=2026-06-01&endDate=2026-06-03" \
+ --output test_data.csv
+```
+
+---
+
+### 8.5 错误测试用例
+
+#### 测试10:日期格式错误
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/date/2026/06/01"
+```
+
+**预期结果**:HTTP 400 Bad Request
+
+---
+
+#### 测试11:日期范围无效
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/range?startDate=2026-06-06&endDate=2026-06-01"
+```
+
+**预期结果**:HTTP 400 Bad Request
+
+---
+
+#### 测试12:日期超出测试数据范围
+
+```bash
+curl -X GET "http://localhost:8080/api/stats/member/date/2025-01-01"
+```
+
+**预期结果**:HTTP 404 Not Found 或返回空数据
+
+---
+
+### 8.6 使用 Postman 测试
+
+导入以下环境变量到 Postman:
+
+| 变量名 | 值 | 说明 |
+|-------|---|------|
+| `base_url` | `http://localhost:8080` | API 基础地址 |
+| `test_start_date` | `2026-06-01` | 测试开始日期 |
+| `test_end_date` | `2026-06-06` | 测试结束日期 |
+
+示例请求:
+
+```
+GET {{base_url}}/api/stats/member/date/2026-06-01
+GET {{base_url}}/api/stats/member/range?startDate={{test_start_date}}&endDate={{test_end_date}}
+GET {{base_url}}/api/stats/booking/summary?startDate={{test_start_date}}&endDate={{test_end_date}}
+```
diff --git a/gym-manage-api/gym-dataCount/.mvn/wrapper/maven-wrapper.properties b/gym-manage-api/gym-dataCount/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..216df05
--- /dev/null
+++ b/gym-manage-api/gym-dataCount/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
diff --git a/gym-manage-api/gym-dataCount/pom.xml b/gym-manage-api/gym-dataCount/pom.xml
new file mode 100644
index 0000000..792603e
--- /dev/null
+++ b/gym-manage-api/gym-dataCount/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+
+ cn.novalon.gym.manage
+ gym-manage-api
+ 1.0.0
+ ../pom.xml
+
+ cn.novalon.gym.manage
+ gym-dataCount
+ 1.0.0
+ gym-dataCount
+ Data Statistics Module
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 21
+
+
+
+ cn.novalon.gym.manage
+ manage-common
+ ${project.version}
+
+
+ cn.novalon.gym.manage
+ manage-db
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springdoc
+ springdoc-openapi-starter-webflux-ui
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.swagger.core.v3
+ swagger-annotations-jakarta
+ 2.2.43
+ compile
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+ cn.novalon.gym.manage
+ gym-member
+ ${project.version}
+
+
+
+
+ cn.novalon.gym.manage
+ gym-groupCourse
+ ${project.version}
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/GymDataCountApplication.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/GymDataCountApplication.java
new file mode 100644
index 0000000..b17a83e
--- /dev/null
+++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/GymDataCountApplication.java
@@ -0,0 +1,29 @@
+package cn.novalon.gym.manage.datacount;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
+
+@SpringBootApplication(scanBasePackages = "cn.novalon.gym.manage", exclude = {
+ ReactiveUserDetailsServiceAutoConfiguration.class })
+@EnableR2dbcRepositories(basePackages = {
+ "cn.novalon.gym.manage.db.dao",
+ "cn.novalon.gym.manage.member.repository",
+ "cn.novalon.gym.manage.groupcourse.dao",
+ "cn.novalon.gym.manage.datacount.dao"
+})
+@ComponentScan(basePackages = "cn.novalon.gym.manage")
+public class GymDataCountApplication {
+
+ private static final Logger logger = LoggerFactory.getLogger(GymDataCountApplication.class);
+
+ public static void main(String[] args) {
+ logger.info("数据统计模块启动中...");
+ SpringApplication.run(GymDataCountApplication.class, args);
+ logger.info("数据统计模块启动完成");
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/config/DataCountRouter.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/config/DataCountRouter.java
new file mode 100644
index 0000000..2749d6c
--- /dev/null
+++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/config/DataCountRouter.java
@@ -0,0 +1,34 @@
+package cn.novalon.gym.manage.datacount.config;
+
+import cn.novalon.gym.manage.datacount.handler.DataCountHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+
+@Configuration
+public class DataCountRouter {
+
+ @Bean
+ public RouterFunction dataCountRoutes(DataCountHandler dataCountHandler) {
+ return route()
+ // ========== 会员数据统计路由 ==========
+ .GET("/api/stats/member/date/{date}", dataCountHandler::getMemberStatisticsByDate)
+ .GET("/api/stats/member/range", dataCountHandler::getMemberStatisticsByDateRange)
+ .GET("/api/stats/member/summary", dataCountHandler::getMemberStatisticsSummary)
+
+ // ========== 预约数据统计路由 ==========
+ .GET("/api/stats/booking/date/{date}", dataCountHandler::getBookingStatisticsByDate)
+ .GET("/api/stats/booking/range", dataCountHandler::getBookingStatisticsByDateRange)
+ .GET("/api/stats/booking/summary", dataCountHandler::getBookingStatisticsSummary)
+
+ // ========== 数据导出路由 ==========
+ .GET("/api/stats/export/member", dataCountHandler::exportMemberStatistics)
+ .GET("/api/stats/export/booking", dataCountHandler::exportBookingStatistics)
+ .GET("/api/stats/export/all", dataCountHandler::exportAllStatistics)
+
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/StatisticsDao.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/StatisticsDao.java
new file mode 100644
index 0000000..d2530ab
--- /dev/null
+++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/dao/StatisticsDao.java
@@ -0,0 +1,175 @@
+package cn.novalon.gym.manage.datacount.dao;
+
+import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 统计数据访问层
+ * 使用 R2dbcEntityTemplate 执行原生SQL查询
+ *
+ * @author 数据统计模块
+ * @date 2026-06-06
+ */
+@Repository
+public class StatisticsDao {
+
+ private final R2dbcEntityTemplate r2dbcEntityTemplate;
+
+ public StatisticsDao(R2dbcEntityTemplate r2dbcEntityTemplate) {
+ this.r2dbcEntityTemplate = r2dbcEntityTemplate;
+ }
+
+ // ========== 会员数据统计 ==========
+
+ public Mono countTotalMembers() {
+ String sql = "SELECT COUNT(*) FROM member_user WHERE is_deleted = FALSE";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countNewMembersByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM member_user WHERE is_deleted = FALSE AND DATE(created_at) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countActiveMembersByDate(LocalDate date) {
+ String sql = "SELECT COUNT(DISTINCT m.id) FROM member_user m LEFT JOIN member_card_record r ON m.id = r.member_id WHERE m.is_deleted = FALSE AND r.deleted_at IS NULL AND DATE(r.created_at) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countMaleMembers() {
+ String sql = "SELECT COUNT(*) FROM member_user WHERE is_deleted = FALSE AND gender = 1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countFemaleMembers() {
+ String sql = "SELECT COUNT(*) FROM member_user WHERE is_deleted = FALSE AND gender = 2";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countCardPurchaseByDate(LocalDate date) {
+ String sql = "SELECT COUNT(DISTINCT r.member_id) FROM member_card_record r WHERE r.deleted_at IS NULL AND DATE(r.created_at) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono calculateAverageAge() {
+ String sql = "SELECT AVG(EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthday))) FROM member_user WHERE is_deleted = FALSE AND birthday IS NOT NULL";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .map(row -> row.get(0, Double.class))
+ .one();
+ }
+
+ public Mono countActiveMembersByTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
+ String sql = "SELECT COUNT(*) FROM member_user WHERE is_deleted = FALSE AND last_login_at >= $1 AND last_login_at < $2";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, startTime)
+ .bind(1, endTime)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ // ========== 预约数据统计 ==========
+
+ public Mono countTotalBookingsByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM group_course_booking WHERE deleted_at IS NULL AND DATE(created_at) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countCancelledBookingsByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM group_course_booking WHERE deleted_at IS NULL AND status = '1' AND DATE(cancel_time) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countAttendedBookingsByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM group_course_booking WHERE deleted_at IS NULL AND status = '2' AND DATE(created_at) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countTotalCoursesByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM group_course WHERE deleted_at IS NULL AND DATE(start_time) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono countFullCoursesByDate(LocalDate date) {
+ String sql = "SELECT COUNT(*) FROM group_course WHERE deleted_at IS NULL AND current_members = max_members AND DATE(start_time) = $1";
+ return r2dbcEntityTemplate.getDatabaseClient()
+ .sql(sql)
+ .bind(0, date)
+ .map(row -> row.get(0, Integer.class))
+ .one();
+ }
+
+ public Mono