From 7a9414581951e2cf29a3ba3a63f6ebaf1b0d0884 Mon Sep 17 00:00:00 2001 From: liwentao Date: Thu, 11 Jun 2026 13:57:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=A2=E8=AF=BE=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=EF=BC=8C=E7=B1=BB=E5=9E=8B=E6=A0=87=E7=AD=BE=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gym-manage-api/docs/groupcourse-api.md | 796 +++++++++++++++++- gym-manage-api/exmp.txt | 87 ++ gym-manage-api/gym-groupCourse/pom.xml | 5 + .../converter/GroupCourseConverter.java | 31 + .../groupcourse/dao/CourseLabelDao.java | 27 + .../groupcourse/dao/CourseTypeLabelDao.java | 38 + .../groupcourse/dao/GroupCourseDao.java | 2 + .../groupcourse/dao/GroupCourseTypeDao.java | 32 + .../groupcourse/domain/CourseLabel.java | 43 + .../groupcourse/domain/GroupCourseDetail.java | 254 ++++++ .../groupcourse/domain/GroupCourseType.java | 113 +++ .../groupcourse/entity/CourseLabelEntity.java | 45 + .../entity/CourseTypeLabelEntity.java | 33 + .../entity/GroupCourseTypeEntity.java | 57 ++ .../handler/CourseLabelHandler.java | 217 +++++ .../handler/GroupCourseHandler.java | 9 + .../handler/GroupCourseTypeHandler.java | 137 +++ .../repository/ICourseLabelRepository.java | 32 + .../repository/IGroupCourseRepository.java | 2 + .../IGroupCourseTypeRepository.java | 28 + .../impl/CourseLabelRepository.java | 161 ++++ .../impl/GroupCourseRepository.java | 6 + .../impl/GroupCourseTypeRepository.java | 132 +++ .../service/ICourseLabelService.java | 30 + .../service/IGroupCourseService.java | 2 + .../service/IGroupCourseTypeService.java | 32 + .../service/impl/CourseLabelService.java | 112 +++ .../service/impl/GroupCourseService.java | 102 ++- .../service/impl/GroupCourseTypeService.java | 86 ++ gym-manage-api/log.text | 375 +++++---- .../gym/manage/app/config/SystemRouter.java | 41 +- .../V15__Create_GroupCourse_Type_table.sql | 76 ++ .../V16__Create_Course_Label_tables.sql | 73 ++ 33 files changed, 3054 insertions(+), 162 deletions(-) create mode 100644 gym-manage-api/exmp.txt create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseLabelDao.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseTypeLabelDao.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseTypeDao.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/CourseLabel.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseType.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseLabelEntity.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseTypeLabelEntity.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseTypeEntity.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/CourseLabelHandler.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseTypeHandler.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/ICourseLabelRepository.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseTypeRepository.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/CourseLabelRepository.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseTypeRepository.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/ICourseLabelService.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseTypeService.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseTypeService.java create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql diff --git a/gym-manage-api/docs/groupcourse-api.md b/gym-manage-api/docs/groupcourse-api.md index 17509e4..cad1497 100644 --- a/gym-manage-api/docs/groupcourse-api.md +++ b/gym-manage-api/docs/groupcourse-api.md @@ -26,11 +26,32 @@ - [查询会员预约记录](#查询会员预约记录) - [查询预约详情](#查询预约详情) - [查询课程预约记录](#查询课程预约记录) -5. [数据模型](#数据模型) +5. [团课类型管理接口](#团课类型管理接口) + - [获取所有团课类型](#获取所有团课类型) + - [根据ID获取团课类型](#根据ID获取团课类型) + - [搜索团课类型](#搜索团课类型) + - [根据分类获取团课类型](#根据分类获取团课类型) + - [获取所有分类](#获取所有分类) + - [创建团课类型](#创建团课类型) + - [更新团课类型](#更新团课类型) + - [删除团课类型](#删除团课类型) +6. [团课标签管理接口](#团课标签管理接口) + - [获取所有标签](#获取所有标签) + - [根据ID获取标签](#根据ID获取标签) + - [搜索标签](#搜索标签) + - [获取类型的标签](#获取类型的标签) + - [创建标签](#创建标签) + - [更新标签](#更新标签) + - [删除标签](#删除标签) + - [为类型添加标签](#为类型添加标签) + - [从类型移除标签](#从类型移除标签) + - [清空类型标签](#清空类型标签) +7. [数据模型](#数据模型) - [GroupCourse(团课)](#GroupCourse团课) - [GroupCourseBooking(团课预约)](#GroupCourseBooking团课预约) -6. [状态码说明](#状态码说明) -7. [业务规则](#业务规则) + - [GroupCourseType(团课类型)](#GroupCourseType团课类型) +7. [状态码说明](#状态码说明) +8. [业务规则](#业务规则) --- @@ -178,6 +199,78 @@ --- +### 根据ID获取团课完整信息 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/{id}/detail` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "courseName": "瑜伽入门", + "coachId": 1, + "courseType": 1, + "startTime": "2026-06-02T09:00:00", + "endTime": "2026-06-02T10:00:00", + "maxMembers": 20, + "currentMembers": 15, + "status": 0, + "location": "健身房A区", + "coverImage": "https://example.com/yoga.jpg", + "description": "适合初学者的瑜伽课程", + "pointCardAmount": 1, + "storedValueAmount": 50.00, + "typeName": "瑜伽入门", + "typeCategory": "柔韧与平衡类", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "calculatedDifficulty": 2, + "typeInfo": { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "labels": [ + {"id": 1, "labelName": "适合新手", "color": "#52c41a"}, + {"id": 3, "labelName": "减压放松", "color": "#1890ff"} + ] + }, + "labels": [ + {"id": 1, "labelName": "适合新手", "color": "#52c41a"}, + {"id": 3, "labelName": "减压放松", "color": "#1890ff"} + ], + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +**说明**: 此接口返回团课的完整信息,包括: +- 团课基础信息 +- 团课对应的类型信息(包含基础难度、综合难度、难度等级等) +- 该类型的所有标签信息 + +--- + ### 创建团课 | 属性 | 值 | @@ -628,6 +721,625 @@ --- +## 团课类型管理接口 + +### 获取所有团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| includeDeleted | boolean | 否 | false | 是否包含已删除的类型 | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + } +] +``` + +--- + +### 根据ID获取团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 搜索团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/search` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词(匹配类型名称) | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "category": "柔韧与平衡类" + } +] +``` + +--- + +### 根据分类获取团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/category/{category}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| category | String | 是 | 分类名称 | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词 | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "category": "柔韧与平衡类" + } +] +``` + +--- + +### 获取所有分类 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/categories` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**成功响应** (200 OK): + +```json +["基础有氧与热身", "固定器械训练", "自重基础动作", "自由重量杠铃/哑铃", "高强度与爆发力", "柔韧与平衡类"] +``` + +--- + +### 创建团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/types` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求体**: + +```json +{ + "typeName": "核心力量训练", + "baseDifficulty": 4, + "description": "针对核心肌群的专项训练课程", + "category": "自重基础动作" +} +``` + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| typeName | String | **是** | - | 类型名称 | +| baseDifficulty | Integer | 否 | 1 | 基础难度(1-10) | +| description | String | 否 | - | 类型描述 | +| category | String | 否 | - | 分类 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型创建成功", + "data": { + "id": 40, + "typeName": "核心力量训练", + "baseDifficulty": 4, + "calculatedDifficulty": 4, + "difficultyLevel": "中级", + "description": "针对核心肌群的专项训练课程", + "category": "自重基础动作" + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "类型名称不能为空" +} +``` + +--- + +### 更新团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | PUT | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**请求体**: + +```json +{ + "typeName": "核心力量训练进阶", + "baseDifficulty": 6, + "description": "进阶核心训练课程" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeName | String | 否 | 类型名称 | +| baseDifficulty | Integer | 否 | 基础难度(1-10) | +| description | String | 否 | 类型描述 | +| category | String | 否 | 分类 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型更新成功", + "data": { + "id": 40, + "typeName": "核心力量训练进阶", + "baseDifficulty": 6, + "calculatedDifficulty": 6, + "difficultyLevel": "中高级", + "description": "进阶核心训练课程", + "category": "自重基础动作" + } +} +``` + +--- + +### 删除团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型删除成功" +} +``` + +--- + +## 团课标签管理接口 + +### 获取所有标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + }, + { + "id": 2, + "labelName": "中级过渡", + "color": "#faad14", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + } +] +``` + +--- + +### 根据ID获取标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "labelName": "适合新手", + "color": "#52c41a", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 搜索标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels/search` | +| **所属文件** | `CourseLabelHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词(匹配标签名称) | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a" + } +] +``` + +--- + +### 获取类型的标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a" + }, + { + "id": 3, + "labelName": "减压放松", + "color": "#1890ff" + } +] +``` + +--- + +### 创建标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**请求体**: + +```json +{ + "labelName": "燃脂塑形", + "color": "#f5222d" +} +``` + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| labelName | String | **是** | - | 标签名称(最大50字符) | +| color | String | 否 | #1890ff | 标签颜色(十六进制颜色值) | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签创建成功", + "data": { + "id": 15, + "labelName": "燃脂塑形", + "color": "#f5222d" + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "标签名称已存在" +} +``` + +--- + +### 更新标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | PUT | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**请求体**: + +```json +{ + "labelName": "燃脂塑形进阶", + "color": "#fa541c" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| labelName | String | 否 | 标签名称(最大50字符) | +| color | String | 否 | 标签颜色(十六进制颜色值) | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签更新成功", + "data": { + "id": 15, + "labelName": "燃脂塑形进阶", + "color": "#fa541c" + } +} +``` + +--- + +### 删除标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签删除成功" +} +``` + +--- + +### 为类型添加标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**请求体**: + +```json +{ + "labelIds": [1, 3, 5] +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| labelIds | List\ | **是** | 要添加的标签ID列表 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签添加成功" +} +``` + +--- + +### 从类型移除标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels/{labelId}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | +| labelId | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签移除成功" +} +``` + +--- + +### 清空类型标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签清空成功" +} +``` + +--- + ## 数据模型 ### GroupCourse(团课) @@ -675,6 +1387,84 @@ | updatedAt | LocalDateTime | 更新时间 | | deletedAt | LocalDateTime | 删除时间(软删除) | +### GroupCourseType(团课类型) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| typeName | String | 类型名称 | +| baseDifficulty | Integer | 基础难度(1-10) | +| calculatedDifficulty | Integer | 综合难度系数(预留扩展字段) | +| difficultyLevel | String | 难度等级描述(初级/中级/中高级/高级/专家级) | +| description | String | 类型描述 | +| category | String | 分类(如:有氧、力量、柔韧等) | +| labels | List\ | 标签列表 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### CourseLabel(团课标签) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| labelName | String | 标签名称(最大50字符) | +| color | String | 标签颜色(十六进制颜色值,默认#1890ff) | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### GroupCourseDetail(团课完整信息) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| courseName | String | 课程名称 | +| coachId | Long | 教练ID | +| courseType | Long | 课程类型ID | +| startTime | LocalDateTime | 开始时间 | +| endTime | LocalDateTime | 结束时间 | +| maxMembers | Integer | 最大参与人数 | +| currentMembers | Integer | 当前参与人数 | +| status | Long | 状态(0-正常,1-已取消,2-已结束) | +| location | String | 上课地点 | +| coverImage | String | 封面图URL | +| description | String | 课程描述 | +| pointCardAmount | Integer | 点卡额度(消耗次数) | +| storedValueAmount | BigDecimal | 储值卡额度(消耗金额) | +| typeName | String | 类型名称(快捷访问) | +| typeCategory | String | 类型分类(快捷访问) | +| baseDifficulty | Integer | 基础难度(快捷访问) | +| difficultyLevel | String | 难度等级描述(快捷访问) | +| calculatedDifficulty | Integer | 综合难度系数(快捷访问) | +| typeInfo | GroupCourseType | 类型信息 | +| labels | List\ | 标签列表 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | + +**难度等级对应关系**: + +| 基础难度 | 难度等级 | +|----------|----------| +| 1-2 | 初级 | +| 3-4 | 中级 | +| 5-6 | 中高级 | +| 7-8 | 高级 | +| 9-10 | 专家级 | + +**难度扩展说明**: + +`calculatedDifficulty` 字段为预留扩展字段,当前实现仅返回 `baseDifficulty`。未来可扩展的影响因素包括: + +1. **课程时长系数**:时长越长难度越高 +2. **教练难度调整系数**:教练可根据实际情况微调 +3. **会员等级适配系数**:根据会员等级动态调整显示难度 +4. **课程强度系数**:高强度课程难度加成 + --- ## 状态码说明 diff --git a/gym-manage-api/exmp.txt b/gym-manage-api/exmp.txt new file mode 100644 index 0000000..e9616bf --- /dev/null +++ b/gym-manage-api/exmp.txt @@ -0,0 +1,87 @@ +一、基础有氧与热身(难度 1-3) + +主要是低冲击、低技巧,用于建立运动基础。 + + 慢走/椭圆机轻松模式:1(几乎无难度,适合所有人) + + 固定自行车(低阻力):2(注意座椅高度调节即可) + + 跑步机慢跑:3(需要基本协调性,膝盖有压力) + + 跳绳(连续基础跳):3(需要手脚配合,心肺要求明显) + +二、固定器械训练(难度 2-5) + +轨迹固定,主要考验力量和耐力,技巧要求低。 + + 坐姿腿屈伸/腿弯举:2(很容易找到发力感) + + 坐姿推胸机:3(需注意肩胛后收,避免耸肩) + + 高位下拉(坐姿):3(需控制不要过度后仰) + + 史密斯机深蹲:4(轨迹固定,但需保持核心稳定) + + 蝴蝶机夹胸:3(易用肘关节代偿,需锁定肩关节) + +三、自重基础动作(难度 3-7) + +需要一定的力量-体重比和身体控制能力。 + + 平板支撑:3(耐力考验,技巧低) + + 跪姿俯卧撑:3(上肢力量较弱者首选) + + 标准俯卧撑:5(需核心收紧,身体成直线) + + 引体向上(弹力带辅助):6(背部和手臂力量要求高) + + 标准引体向上:8(力量-体重比极高,多数男性无法完成1次) + + 徒手深蹲:3(注意膝盖方向与背部直立) + + 单腿深蹲(手枪蹲):8(需要极高下肢力量、柔韧性和平衡) + +四、自由重量杠铃/哑铃(难度 5-9) + +技巧风险最高,需要神经系统协调和长期动作打磨。 + + 哑铃二头弯举:4(容易晃动借力,但较安全) + + 哑铃侧平举:5(极易用斜方肌代偿,真正练到三角肌中束很难) + + 杠铃卧推:7(肩关节压力大,起桥、沉肩、稳定手腕均有技巧,有压伤风险) + + 杠铃深蹲(颈后):8(全身协调性、核心抗压、杠位放置、呼吸模式,学习曲线陡峭) + + 传统硬拉:9(风险极高,需要精确的脊柱中立、髋铰链、背阔肌收紧,错误时伤腰) + + 高翻/抓举(奥运举重):10(需要爆发力、柔韧、精准衔接,非数月训练不能掌握) + +五、高强度与爆发力(难度 6-10) + +对心肺、神经系统和恢复能力要求极高。 + + 波比跳(标准版):6(连续做时心肺压力极大) + + 冲刺跑(短跑):7(对腘绳肌和脚踝爆发力要求高) + + 跳箱(合理高度):6(需要落地缓冲技巧) + + 负重雪橇推:6(主要考验腿部耐力和意志力) + + 双力臂(引体向上后翻腕上杠):9(需要爆发引体 + 极高相对力量) + +六、柔韧与平衡类(难度 3-8) + +考验本体感觉和关节活动度。 + + 静态拉伸(坐姿体前屈):2(无风险,但需要坚持) + + 瑜伽下犬式:3(常见,但需背部与手臂对齐) + + 单腿罗马尼亚硬拉(徒手):6(极考验平衡和髋稳定) + + 全深蹲(脚跟贴地,亚洲蹲):5(踝关节灵活度限制多数人) + + 竖叉/横叉:8(需要数月甚至数年拉伸) \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml index 2b8ae6b..a29e14e 100644 --- a/gym-manage-api/gym-groupCourse/pom.xml +++ b/gym-manage-api/gym-groupCourse/pom.xml @@ -35,6 +35,11 @@ manage-common ${project.version} + + cn.novalon.gym.manage + manage-sys + ${project.version} + cn.novalon.gym.manage manage-db diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/converter/GroupCourseConverter.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/converter/GroupCourseConverter.java index e8bcf07..34d629b 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/converter/GroupCourseConverter.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/converter/GroupCourseConverter.java @@ -4,8 +4,10 @@ package cn.novalon.gym.manage.groupcourse.converter; import cn.hutool.core.bean.BeanUtil; import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; import cn.novalon.gym.manage.groupcourse.entity.GroupCourseBookingEntity; import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity; +import cn.novalon.gym.manage.groupcourse.entity.GroupCourseTypeEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -124,4 +126,33 @@ public class GroupCourseConverter { .map(this::toBookingEntity) .collect(Collectors.toList()); } + + /** + * 将团课类型实体转换为领域模型 + */ + public GroupCourseType toGroupCourseType(GroupCourseTypeEntity entity){ + if(entity == null){ + return null; + } + GroupCourseType groupCourseType = new GroupCourseType(); + BeanUtil.copyProperties(entity, groupCourseType); + log.debug("转换团课类型实体到领域模型:typeId={}", entity.getId()); + return groupCourseType; + } + + /** + * 将团课类型领域模型转换为实体 + */ + public GroupCourseTypeEntity toGroupCourseTypeEntity(GroupCourseType domain){ + if(domain == null){ + return null; + } + GroupCourseTypeEntity entity = new GroupCourseTypeEntity(); + BeanUtil.copyProperties(domain, entity); + if (domain.getId() != null) { + entity.markNotNew(); + } + log.debug("转换团课类型领域模型到实体:typeId={}", domain.getId()); + return entity; + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseLabelDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseLabelDao.java new file mode 100644 index 0000000..86c8a9b --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseLabelDao.java @@ -0,0 +1,27 @@ +package cn.novalon.gym.manage.groupcourse.dao; + +import cn.novalon.gym.manage.groupcourse.entity.CourseLabelEntity; +import org.springframework.data.r2dbc.repository.Modifying; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; + +@Repository +public interface CourseLabelDao extends R2dbcRepository { + + Mono findByIdIsAndDeletedAtIsNull(Long id); + + Flux findAllByDeletedAtIsNull(); + + Flux findByLabelNameContainingAndDeletedAtIsNull(String labelName); + + Mono findByLabelNameAndDeletedAtIsNull(String labelName); + + @Modifying + @Query("UPDATE course_label SET deleted_at = :deletedAt WHERE id = :id") + Mono softDelete(Long id, LocalDateTime deletedAt); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseTypeLabelDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseTypeLabelDao.java new file mode 100644 index 0000000..9f11e88 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/CourseTypeLabelDao.java @@ -0,0 +1,38 @@ +package cn.novalon.gym.manage.groupcourse.dao; + +import cn.novalon.gym.manage.groupcourse.entity.CourseTypeLabelEntity; +import org.springframework.data.r2dbc.repository.Modifying; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface CourseTypeLabelDao extends R2dbcRepository { + + Flux findByTypeIdAndDeletedAtIsNull(Long typeId); + + Flux findByLabelIdAndDeletedAtIsNull(Long labelId); + + Mono findByTypeIdAndLabelIdAndDeletedAtIsNull(Long typeId, Long labelId); + + @Modifying + @Query("UPDATE course_type_label SET deleted_at = :deletedAt WHERE type_id = :typeId AND label_id = :labelId") + Mono deleteByTypeIdAndLabelId(Long typeId, Long labelId, LocalDateTime deletedAt); + + @Modifying + @Query("UPDATE course_type_label SET deleted_at = :deletedAt WHERE type_id = :typeId") + Mono deleteByTypeId(Long typeId, LocalDateTime deletedAt); + + @Modifying + @Query("DELETE FROM course_type_label WHERE type_id = :typeId AND label_id = :labelId") + Mono physicalDeleteByTypeIdAndLabelId(Long typeId, Long labelId); + + @Modifying + @Query("UPDATE course_type_label SET deleted_at = :deletedAt WHERE label_id = :labelId") + Mono deleteByLabelId(Long labelId, LocalDateTime deletedAt); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java index 9a0000b..e580d6c 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java @@ -36,4 +36,6 @@ public interface GroupCourseDao extends R2dbcRepository @Modifying @Query("UPDATE group_course SET deleted_at = :deletedAt WHERE id = :id") Mono softDelete(Long id, LocalDateTime deletedAt); + + Flux findByCourseTypeAndDeletedAtIsNull(Long courseType); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseTypeDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseTypeDao.java new file mode 100644 index 0000000..08183d6 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseTypeDao.java @@ -0,0 +1,32 @@ +package cn.novalon.gym.manage.groupcourse.dao; + +import cn.novalon.gym.manage.groupcourse.entity.GroupCourseTypeEntity; +import org.springframework.data.domain.Sort; +import org.springframework.data.r2dbc.repository.Modifying; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; + +@Repository +public interface GroupCourseTypeDao extends R2dbcRepository { + + Mono findByIdIsAndDeletedAtIsNull(Long id); + + Flux findAllByDeletedAtIsNull(); + + Flux findAllByDeletedAtIsNull(Sort sort); + + Flux findByTypeNameContainingAndDeletedAtIsNull(String typeName); + + Flux findByCategoryAndDeletedAtIsNull(String category); + + Mono findByTypeNameAndDeletedAtIsNull(String typeName); + + @Modifying + @Query("UPDATE group_course_type SET deleted_at = :deletedAt WHERE id = :id") + Mono softDelete(Long id, LocalDateTime deletedAt); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/CourseLabel.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/CourseLabel.java new file mode 100644 index 0000000..9964bae --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/CourseLabel.java @@ -0,0 +1,43 @@ +package cn.novalon.gym.manage.groupcourse.domain; + +import cn.novalon.gym.manage.sys.core.domain.BaseDomain; +import io.swagger.v3.oas.annotations.media.Schema; + +public class CourseLabel extends BaseDomain { + + //标签名称 + @Schema(description = "标签名称", example = "适合新手") + private String labelName; + + //标签颜色(十六进制) + @Schema(description = "标签颜色(十六进制)", example = "#52c41a") + private String color; + + //标签描述 + @Schema(description = "标签描述", example = "适合健身初学者") + private String description; + + public String getLabelName() { + return labelName; + } + + public void setLabelName(String labelName) { + this.labelName = labelName; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java new file mode 100644 index 0000000..d8ae246 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java @@ -0,0 +1,254 @@ +package cn.novalon.gym.manage.groupcourse.domain; + +import cn.novalon.gym.manage.sys.core.domain.BaseDomain; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 团课完整信息领域模型 + * 包含团课基础信息、关联的类型信息以及类型的标签信息 + */ +public class GroupCourseDetail extends BaseDomain { + + // ===== 团课基础信息 ===== + + @Schema(description = "课程名称", example = "瑜伽入门") + private String courseName; + + @Schema(description = "教练ID", example = "1") + private Long coachId; + + @Schema(description = "课程类型ID", example = "1") + private Long courseType; + + @Schema(description = "开始时间", example = "2026-06-02T09:00:00") + private LocalDateTime startTime; + + @Schema(description = "结束时间", example = "2026-06-02T10:00:00") + private LocalDateTime endTime; + + @Schema(description = "最大参与人数", example = "20") + private Integer maxMembers; + + @Schema(description = "当前参与人数", example = "15") + private Integer currentMembers; + + @Schema(description = "课程状态", example = "0") + private Long status; + + @Schema(description = "上课地点", example = "健身房A区") + private String location; + + @Schema(description = "封面图URL", example = "https://example.com/yoga.jpg") + private String coverImage; + + @Schema(description = "课程描述", example = "适合初学者的瑜伽课程") + private String description; + + @Schema(description = "点卡额度(消耗次数)", example = "1") + private Integer pointCardAmount; + + @Schema(description = "储值卡额度(消耗金额)", example = "50.00") + private BigDecimal storedValueAmount; + + // ===== 关联的类型信息 ===== + + @Schema(description = "类型信息") + private GroupCourseType typeInfo; + + // ===== 快捷访问属性(从类型信息派生)===== + + @Schema(description = "类型名称", example = "瑜伽入门") + private String typeName; + + @Schema(description = "类型分类", example = "柔韧与平衡类") + private String typeCategory; + + @Schema(description = "基础难度", example = "2") + private Integer baseDifficulty; + + @Schema(description = "难度等级描述", example = "初级") + private String difficultyLevel; + + @Schema(description = "综合难度系数", example = "2") + private Integer calculatedDifficulty; + + // ===== 标签信息(从类型标签派生)===== + + @Schema(description = "标签列表") + private List labels; + + // ===== Getters and Setters ===== + + public String getCourseName() { + return courseName; + } + + public void setCourseName(String courseName) { + this.courseName = courseName; + } + + public Long getCoachId() { + return coachId; + } + + public void setCoachId(Long coachId) { + this.coachId = coachId; + } + + public Long getCourseType() { + return courseType; + } + + public void setCourseType(Long courseType) { + this.courseType = courseType; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public Integer getMaxMembers() { + return maxMembers; + } + + public void setMaxMembers(Integer maxMembers) { + this.maxMembers = maxMembers; + } + + public Integer getCurrentMembers() { + return currentMembers; + } + + public void setCurrentMembers(Integer currentMembers) { + this.currentMembers = currentMembers; + } + + public Long getStatus() { + return status; + } + + public void setStatus(Long status) { + this.status = status; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getCoverImage() { + return coverImage; + } + + public void setCoverImage(String coverImage) { + this.coverImage = coverImage; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getPointCardAmount() { + return pointCardAmount; + } + + public void setPointCardAmount(Integer pointCardAmount) { + this.pointCardAmount = pointCardAmount; + } + + public BigDecimal getStoredValueAmount() { + return storedValueAmount; + } + + public void setStoredValueAmount(BigDecimal storedValueAmount) { + this.storedValueAmount = storedValueAmount; + } + + public GroupCourseType getTypeInfo() { + return typeInfo; + } + + public void setTypeInfo(GroupCourseType typeInfo) { + this.typeInfo = typeInfo; + // 同步派生属性 + if (typeInfo != null) { + this.typeName = typeInfo.getTypeName(); + this.typeCategory = typeInfo.getCategory(); + this.baseDifficulty = typeInfo.getBaseDifficulty(); + this.difficultyLevel = typeInfo.getDifficultyLevel(); + this.calculatedDifficulty = typeInfo.getCalculatedDifficulty(); + this.labels = typeInfo.getLabels(); + } + } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public String getTypeCategory() { + return typeCategory; + } + + public void setTypeCategory(String typeCategory) { + this.typeCategory = typeCategory; + } + + public Integer getBaseDifficulty() { + return baseDifficulty; + } + + public void setBaseDifficulty(Integer baseDifficulty) { + this.baseDifficulty = baseDifficulty; + } + + public String getDifficultyLevel() { + return difficultyLevel; + } + + public void setDifficultyLevel(String difficultyLevel) { + this.difficultyLevel = difficultyLevel; + } + + public Integer getCalculatedDifficulty() { + return calculatedDifficulty; + } + + public void setCalculatedDifficulty(Integer calculatedDifficulty) { + this.calculatedDifficulty = calculatedDifficulty; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseType.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseType.java new file mode 100644 index 0000000..7653f56 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseType.java @@ -0,0 +1,113 @@ +package cn.novalon.gym.manage.groupcourse.domain; + +import cn.novalon.gym.manage.sys.core.domain.BaseDomain; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.List; + +public class GroupCourseType extends BaseDomain { + + //类型名称 + @Schema(description = "类型名称", example = "瑜伽入门") + private String typeName; + + //基础难度(1-10) + @Schema(description = "基础难度(1-10)", example = "2") + private Integer baseDifficulty; + + //类型描述 + @Schema(description = "类型描述", example = "适合初学者的瑜伽课程") + private String description; + + //分类(如:有氧、力量、柔韧等) + @Schema(description = "分类", example = "柔韧与平衡类") + private String category; + + //标签列表 + @Schema(description = "标签列表") + private List labels = new ArrayList<>(); + + /** + * 计算综合难度系数 + * + * 当前实现仅返回基础难度,为后续扩展预留空间。 + * 未来可扩展的影响因素包括: + * 1. 课程时长系数(时长越长难度越高) + * 2. 教练难度调整系数(教练可根据实际情况微调) + * 3. 会员等级适配系数(根据会员等级动态调整显示难度) + * 4. 课程强度系数(高强度课程难度加成) + * + * @return 综合难度系数(1-10) + */ + @Schema(description = "综合难度系数(预留扩展字段)", example = "2") + public Integer getCalculatedDifficulty() { + // TODO: 预留扩展点 - 未来可在此处添加更多难度计算逻辑 + // 例如:return calculateDynamicDifficulty(baseDifficulty, additionalFactors...); + return this.baseDifficulty != null ? this.baseDifficulty : 1; + } + + /** + * 获取难度等级描述 + * 将数字难度转换为友好的文字描述 + * + * @return 难度等级描述 + */ + @Schema(description = "难度等级描述", example = "初级") + public String getDifficultyLevel() { + if (baseDifficulty == null) { + return "未知"; + } + if (baseDifficulty <= 2) { + return "初级"; + } else if (baseDifficulty <= 4) { + return "中级"; + } else if (baseDifficulty <= 6) { + return "中高级"; + } else if (baseDifficulty <= 8) { + return "高级"; + } else { + return "专家级"; + } + } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public Integer getBaseDifficulty() { + return baseDifficulty; + } + + public void setBaseDifficulty(Integer baseDifficulty) { + this.baseDifficulty = baseDifficulty; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseLabelEntity.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseLabelEntity.java new file mode 100644 index 0000000..9b1633e --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseLabelEntity.java @@ -0,0 +1,45 @@ +package cn.novalon.gym.manage.groupcourse.entity; + +import cn.novalon.gym.manage.db.entity.BaseEntity; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("course_label") +public class CourseLabelEntity extends BaseEntity { + + //标签名称 + @Column("label_name") + private String labelName; + + //标签颜色(十六进制) + @Column("color") + private String color; + + //标签描述 + @Column("description") + private String description; + + public String getLabelName() { + return labelName; + } + + public void setLabelName(String labelName) { + this.labelName = labelName; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseTypeLabelEntity.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseTypeLabelEntity.java new file mode 100644 index 0000000..b773e76 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/CourseTypeLabelEntity.java @@ -0,0 +1,33 @@ +package cn.novalon.gym.manage.groupcourse.entity; + +import cn.novalon.gym.manage.db.entity.BaseEntity; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("course_type_label") +public class CourseTypeLabelEntity extends BaseEntity { + + //团课类型ID + @Column("type_id") + private Long typeId; + + //标签ID + @Column("label_id") + private Long labelId; + + public Long getTypeId() { + return typeId; + } + + public void setTypeId(Long typeId) { + this.typeId = typeId; + } + + public Long getLabelId() { + return labelId; + } + + public void setLabelId(Long labelId) { + this.labelId = labelId; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseTypeEntity.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseTypeEntity.java new file mode 100644 index 0000000..d5ed990 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseTypeEntity.java @@ -0,0 +1,57 @@ +package cn.novalon.gym.manage.groupcourse.entity; + +import cn.novalon.gym.manage.db.entity.BaseEntity; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("group_course_type") +public class GroupCourseTypeEntity extends BaseEntity { + + //类型名称 + @Column("type_name") + private String typeName; + + //基础难度(1-10) + @Column("base_difficulty") + private Integer baseDifficulty; + + //类型描述 + @Column("description") + private String description; + + //分类(如:有氧、力量、柔韧等) + @Column("category") + private String category; + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public Integer getBaseDifficulty() { + return baseDifficulty; + } + + public void setBaseDifficulty(Integer baseDifficulty) { + this.baseDifficulty = baseDifficulty; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/CourseLabelHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/CourseLabelHandler.java new file mode 100644 index 0000000..ea3a6b3 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/CourseLabelHandler.java @@ -0,0 +1,217 @@ +package cn.novalon.gym.manage.groupcourse.handler; + +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; +import cn.novalon.gym.manage.groupcourse.service.ICourseLabelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@Tag(name = "团课标签管理", description = "团课标签相关操作") +public class CourseLabelHandler { + + private final ICourseLabelService courseLabelService; + + public CourseLabelHandler(ICourseLabelService courseLabelService) { + this.courseLabelService = courseLabelService; + } + + @Operation(summary = "获取所有标签", description = "获取系统中所有标签列表") + public Mono getAllLabels(ServerRequest request) { + return ServerResponse.ok() + .body(courseLabelService.findAll(), CourseLabel.class); + } + + @Operation(summary = "根据ID获取标签", description = "根据ID获取标签详情") + public Mono getLabelById(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return courseLabelService.findById(id) + .flatMap(label -> ServerResponse.ok().bodyValue(label)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + @Operation(summary = "搜索标签", description = "根据关键词搜索标签") + public Mono searchLabels(ServerRequest request) { + String keyword = request.queryParam("keyword").orElse(""); + return ServerResponse.ok() + .body(courseLabelService.findByKeyword(keyword), CourseLabel.class); + } + + @Operation(summary = "创建标签", description = "创建新的标签") + public Mono createLabel(ServerRequest request) { + return request.bodyToMono(CourseLabel.class) + .flatMap(courseLabel -> { + if (courseLabel.getLabelName() == null || courseLabel.getLabelName().isEmpty()) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "标签名称不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + if (courseLabel.getLabelName().length() > 50) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "标签名称不能超过50个字符"); + return ServerResponse.badRequest().bodyValue(error); + } + + if (courseLabel.getColor() == null || courseLabel.getColor().isEmpty()) { + courseLabel.setColor("#1890ff"); + } + + return courseLabelService.create(courseLabel) + .flatMap(label -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签创建成功"); + response.put("data", label); + return ServerResponse.ok().bodyValue(response); + }) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + }); + } + + @Operation(summary = "更新标签", description = "更新指定标签信息") + public Mono updateLabel(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + + return request.bodyToMono(CourseLabel.class) + .flatMap(courseLabel -> { + if (courseLabel.getLabelName() != null && courseLabel.getLabelName().length() > 50) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "标签名称不能超过50个字符"); + return ServerResponse.badRequest().bodyValue(error); + } + + return courseLabelService.update(id, courseLabel) + .flatMap(label -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签更新成功"); + response.put("data", label); + return ServerResponse.ok().bodyValue(response); + }) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + }); + } + + @Operation(summary = "删除标签", description = "删除指定标签(软删除)") + public Mono deleteLabel(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + + return courseLabelService.delete(id) + .then(Mono.defer(() -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签删除成功"); + return ServerResponse.ok().bodyValue(response); + })) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + } + + @Operation(summary = "获取类型的标签", description = "获取指定团课类型的所有标签") + public Mono getLabelsByTypeId(ServerRequest request) { + Long typeId = Long.valueOf(request.pathVariable("typeId")); + return courseLabelService.findByTypeId(typeId) + .collectList() + .flatMap(list -> ServerResponse.ok().bodyValue(list)); + } + + @Operation(summary = "为类型添加标签", description = "为指定团课类型添加标签") + public Mono addLabelsToType(ServerRequest request) { + Long typeId = Long.valueOf(request.pathVariable("typeId")); + + return request.bodyToMono(Map.class) + .flatMap(body -> { + @SuppressWarnings("unchecked") + List labelIdsInt = (List) body.get("labelIds"); + + if (labelIdsInt == null || labelIdsInt.isEmpty()) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "labelIds不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + List labelIds = labelIdsInt.stream() + .map(Integer::longValue) + .collect(java.util.stream.Collectors.toList()); + + return courseLabelService.addLabelsToType(typeId, labelIds) + .then(Mono.defer(() -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签添加成功"); + return ServerResponse.ok().bodyValue(response); + })) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + }); + } + + @Operation(summary = "从类型移除标签", description = "从指定团课类型移除标签") + public Mono removeLabelFromType(ServerRequest request) { + Long typeId = Long.valueOf(request.pathVariable("typeId")); + Long labelId = Long.valueOf(request.pathVariable("labelId")); + + return courseLabelService.removeLabelFromType(typeId, labelId) + .then(Mono.defer(() -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签移除成功"); + return ServerResponse.ok().bodyValue(response); + })) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + } + + @Operation(summary = "清空类型标签", description = "清空指定团课类型的所有标签") + public Mono clearLabelsFromType(ServerRequest request) { + Long typeId = Long.valueOf(request.pathVariable("typeId")); + + return courseLabelService.clearLabelsFromType(typeId) + .then(Mono.defer(() -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "标签清空成功"); + return ServerResponse.ok().bodyValue(response); + })) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java index 0b4d38b..bc15e30 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java @@ -4,6 +4,7 @@ package cn.novalon.gym.manage.groupcourse.handler; import cn.novalon.gym.manage.common.dto.PageRequest; import cn.novalon.gym.manage.common.util.RedisUtil; import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail; import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; @@ -76,6 +77,14 @@ public class GroupCourseHandler { .switchIfEmpty(ServerResponse.notFound().build()); } + @Operation(summary = "根据ID获取团课完整信息", description = "根据ID获取团课完整信息,包括团课基础信息、类型信息和标签信息") + public Mono getGroupCourseDetailById(ServerRequest request){ + Long id = Long.valueOf(request.pathVariable("id")); + return groupCourseService.findDetailById(id) + .flatMap(detail -> ServerResponse.ok().bodyValue(detail)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + @Operation(summary = "创建团课", description = "创建新的团课") public Mono createGroupCourse(ServerRequest request) { return request.bodyToMono(GroupCourse.class) diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseTypeHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseTypeHandler.java new file mode 100644 index 0000000..a0b2a57 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseTypeHandler.java @@ -0,0 +1,137 @@ +package cn.novalon.gym.manage.groupcourse.handler; + +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; +import cn.novalon.gym.manage.groupcourse.service.IGroupCourseTypeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +@Component +@Tag(name = "团课类型管理", description = "团课类型及难度相关操作") +public class GroupCourseTypeHandler { + + private final IGroupCourseTypeService groupCourseTypeService; + + public GroupCourseTypeHandler(IGroupCourseTypeService groupCourseTypeService) { + this.groupCourseTypeService = groupCourseTypeService; + } + + @Operation(summary = "获取所有团课类型", description = "获取系统中所有团课类型列表") + public Mono getAllGroupCourseTypes(ServerRequest request) { + boolean includeDeleted = Boolean.valueOf(request.queryParam("includeDeleted").orElse("false")); + return ServerResponse.ok() + .body(groupCourseTypeService.findAll(includeDeleted), GroupCourseType.class); + } + + @Operation(summary = "根据ID获取团课类型", description = "根据ID获取团课类型详情") + public Mono getGroupCourseTypeById(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + return groupCourseTypeService.findById(id) + .flatMap(type -> ServerResponse.ok().bodyValue(type)) + .switchIfEmpty(ServerResponse.notFound().build()); + } + + @Operation(summary = "根据关键词搜索团课类型", description = "根据类型名称关键词搜索团课类型") + public Mono searchGroupCourseTypes(ServerRequest request) { + String keyword = request.queryParam("keyword").orElse(""); + return ServerResponse.ok() + .body(groupCourseTypeService.findByKeyword(keyword), GroupCourseType.class); + } + + @Operation(summary = "根据分类获取团课类型", description = "根据分类获取团课类型列表") + public Mono getGroupCourseTypesByCategory(ServerRequest request) { + String category = request.pathVariable("category"); + String keyword = request.queryParam("keyword").orElse(""); + return ServerResponse.ok() + .body(groupCourseTypeService.findByCategoryAndKeyword(category, keyword), GroupCourseType.class); + } + + @Operation(summary = "获取所有分类", description = "获取所有团课类型分类(去重)") + public Mono getCategories(ServerRequest request) { + return groupCourseTypeService.findCategories() + .collectList() + .flatMap(list -> ServerResponse.ok().bodyValue(list)); + } + + @Operation(summary = "创建团课类型", description = "创建新的团课类型") + public Mono createGroupCourseType(ServerRequest request) { + return request.bodyToMono(GroupCourseType.class) + .flatMap(groupCourseType -> { + if (groupCourseType.getTypeName() == null || groupCourseType.getTypeName().isEmpty()) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "类型名称不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + // 默认基础难度为1 + if (groupCourseType.getBaseDifficulty() == null) { + groupCourseType.setBaseDifficulty(1); + } + + return groupCourseTypeService.create(groupCourseType) + .flatMap(type -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "团课类型创建成功"); + response.put("data", type); + return ServerResponse.ok().bodyValue(response); + }) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + }); + } + + @Operation(summary = "更新团课类型", description = "更新指定团课类型信息") + public Mono updateGroupCourseType(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + + return request.bodyToMono(GroupCourseType.class) + .flatMap(groupCourseType -> { + groupCourseType.setId(id); + return groupCourseTypeService.update(id, groupCourseType) + .flatMap(type -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "团课类型更新成功"); + response.put("data", type); + return ServerResponse.ok().bodyValue(response); + }) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + }); + } + + @Operation(summary = "删除团课类型", description = "删除指定团课类型(软删除)") + public Mono deleteGroupCourseType(ServerRequest request) { + Long id = Long.valueOf(request.pathVariable("id")); + + return groupCourseTypeService.delete(id) + .then(Mono.defer(() -> { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "团课类型删除成功"); + return ServerResponse.ok().bodyValue(response); + })) + .onErrorResume(error -> { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", error.getMessage()); + return ServerResponse.badRequest().bodyValue(response); + }); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/ICourseLabelRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/ICourseLabelRepository.java new file mode 100644 index 0000000..e922354 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/ICourseLabelRepository.java @@ -0,0 +1,32 @@ +package cn.novalon.gym.manage.groupcourse.repository; + +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface ICourseLabelRepository { + + Mono findById(Long id); + + Flux findAll(); + + Flux findByKeyword(String keyword); + + Mono findByLabelName(String labelName); + + Mono save(CourseLabel courseLabel); + + Mono update(CourseLabel courseLabel); + + Mono deleteById(Long id); + + Flux findByTypeId(Long typeId); + + Mono addLabelsToType(Long typeId, List labelIds); + + Mono removeLabelFromType(Long typeId, Long labelId); + + Mono clearLabelsFromType(Long typeId); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java index cee768e..0a0d880 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java @@ -27,4 +27,6 @@ public interface IGroupCourseRepository { Mono deleteById(Long id); Mono updateCurrentMembers(Long id, Integer delta); + + Flux findByCourseType(Long courseType); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseTypeRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseTypeRepository.java new file mode 100644 index 0000000..3a30617 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseTypeRepository.java @@ -0,0 +1,28 @@ +package cn.novalon.gym.manage.groupcourse.repository; + +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface IGroupCourseTypeRepository { + + Mono findById(Long id); + + Flux findAll(); + + Flux findAll(boolean includeDeleted); + + Flux findByKeyword(String keyword); + + Flux findByCategory(String category); + + Flux findByCategoryAndKeyword(String category, String keyword); + + Mono findByTypeName(String typeName); + + Mono save(GroupCourseType groupCourseType); + + Mono update(GroupCourseType groupCourseType); + + Mono deleteById(Long id); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/CourseLabelRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/CourseLabelRepository.java new file mode 100644 index 0000000..27de680 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/CourseLabelRepository.java @@ -0,0 +1,161 @@ +package cn.novalon.gym.manage.groupcourse.repository.impl; + +import cn.novalon.gym.manage.groupcourse.converter.GroupCourseConverter; +import cn.novalon.gym.manage.groupcourse.dao.CourseLabelDao; +import cn.novalon.gym.manage.groupcourse.dao.CourseTypeLabelDao; +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; +import cn.novalon.gym.manage.groupcourse.entity.CourseLabelEntity; +import cn.novalon.gym.manage.groupcourse.entity.CourseTypeLabelEntity; +import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +@Transactional +public class CourseLabelRepository implements ICourseLabelRepository { + + private static final Logger logger = LoggerFactory.getLogger(CourseLabelRepository.class); + + private final CourseLabelDao courseLabelDao; + private final CourseTypeLabelDao courseTypeLabelDao; + private final GroupCourseConverter converter; + + public CourseLabelRepository(CourseLabelDao courseLabelDao, CourseTypeLabelDao courseTypeLabelDao, + GroupCourseConverter converter) { + this.courseLabelDao = courseLabelDao; + this.courseTypeLabelDao = courseTypeLabelDao; + this.converter = converter; + } + + @Override + public Mono findById(Long id) { + return courseLabelDao.findByIdIsAndDeletedAtIsNull(id) + .map(this::toCourseLabel); + } + + @Override + public Flux findAll() { + return courseLabelDao.findAllByDeletedAtIsNull() + .map(this::toCourseLabel); + } + + @Override + public Flux findByKeyword(String keyword) { + if (keyword == null || keyword.isEmpty()) { + return findAll(); + } + return courseLabelDao.findByLabelNameContainingAndDeletedAtIsNull(keyword) + .map(this::toCourseLabel); + } + + @Override + public Mono findByLabelName(String labelName) { + return courseLabelDao.findByLabelNameAndDeletedAtIsNull(labelName) + .map(this::toCourseLabel); + } + + @Override + public Mono save(CourseLabel courseLabel) { + CourseLabelEntity entity = toCourseLabelEntity(courseLabel); + return courseLabelDao.save(entity) + .map(this::toCourseLabel); + } + + @Override + public Mono update(CourseLabel courseLabel) { + return courseLabelDao.findByIdIsAndDeletedAtIsNull(courseLabel.getId()) + .switchIfEmpty(Mono.error(new RuntimeException("标签不存在"))) + .flatMap(existing -> { + existing.markNotNew(); + if (courseLabel.getLabelName() != null) { + existing.setLabelName(courseLabel.getLabelName()); + } + if (courseLabel.getColor() != null) { + existing.setColor(courseLabel.getColor()); + } + if (courseLabel.getDescription() != null) { + existing.setDescription(courseLabel.getDescription()); + } + existing.setUpdatedAt(LocalDateTime.now()); + return courseLabelDao.save(existing); + }) + .map(this::toCourseLabel); + } + + @Override + public Mono deleteById(Long id) { + return courseLabelDao.softDelete(id, LocalDateTime.now()) + .then(courseTypeLabelDao.deleteByLabelId(id, LocalDateTime.now())) + .then(); + } + + @Override + public Flux findByTypeId(Long typeId) { + return courseTypeLabelDao.findByTypeIdAndDeletedAtIsNull(typeId) + .flatMap(typeLabel -> courseLabelDao.findByIdIsAndDeletedAtIsNull(typeLabel.getLabelId())) + .map(this::toCourseLabel); + } + + @Override + public Mono addLabelsToType(Long typeId, List labelIds) { + return Flux.fromIterable(labelIds) + .flatMap(labelId -> { + return courseTypeLabelDao.physicalDeleteByTypeIdAndLabelId(typeId, labelId) + .then(Mono.defer(() -> { + CourseTypeLabelEntity entity = new CourseTypeLabelEntity(); + entity.setTypeId(typeId); + entity.setLabelId(labelId); + return courseTypeLabelDao.save(entity).then(Mono.empty()); + })); + }) + .then(); + } + + @Override + public Mono removeLabelFromType(Long typeId, Long labelId) { + return courseTypeLabelDao.deleteByTypeIdAndLabelId(typeId, labelId, LocalDateTime.now()) + .then(); + } + + @Override + public Mono clearLabelsFromType(Long typeId) { + return courseTypeLabelDao.deleteByTypeId(typeId, LocalDateTime.now()) + .then(); + } + + private CourseLabel toCourseLabel(CourseLabelEntity entity) { + if (entity == null) { + return null; + } + CourseLabel label = new CourseLabel(); + label.setId(entity.getId()); + label.setLabelName(entity.getLabelName()); + label.setColor(entity.getColor()); + label.setDescription(entity.getDescription()); + label.setCreatedAt(entity.getCreatedAt()); + label.setUpdatedAt(entity.getUpdatedAt()); + return label; + } + + private CourseLabelEntity toCourseLabelEntity(CourseLabel domain) { + if (domain == null) { + return null; + } + CourseLabelEntity entity = new CourseLabelEntity(); + entity.setId(domain.getId()); + entity.setLabelName(domain.getLabelName()); + entity.setColor(domain.getColor()); + entity.setDescription(domain.getDescription()); + if (domain.getId() != null) { + entity.markNotNew(); + } + return entity; + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java index ef344d0..eca5901 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java @@ -178,4 +178,10 @@ public class GroupCourseRepository implements IGroupCourseRepository { return Mono.empty(); }); } + + @Override + public Flux findByCourseType(Long courseType) { + return groupCourseDao.findByCourseTypeAndDeletedAtIsNull(courseType) + .map(groupCourseConverter::toDomain); + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseTypeRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseTypeRepository.java new file mode 100644 index 0000000..4d42a1d --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseTypeRepository.java @@ -0,0 +1,132 @@ +package cn.novalon.gym.manage.groupcourse.repository.impl; + +import cn.novalon.gym.manage.groupcourse.converter.GroupCourseConverter; +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseTypeDao; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; +import cn.novalon.gym.manage.groupcourse.entity.GroupCourseTypeEntity; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; + +@Repository +@Transactional +public class GroupCourseTypeRepository implements IGroupCourseTypeRepository { + + private static final Logger logger = LoggerFactory.getLogger(GroupCourseTypeRepository.class); + + private final GroupCourseTypeDao groupCourseTypeDao; + private final GroupCourseConverter converter; + + public GroupCourseTypeRepository(GroupCourseTypeDao groupCourseTypeDao, GroupCourseConverter converter) { + this.groupCourseTypeDao = groupCourseTypeDao; + this.converter = converter; + } + + @Override + public Mono findById(Long id) { + return groupCourseTypeDao.findByIdIsAndDeletedAtIsNull(id) + .map(converter::toGroupCourseType); + } + + @Override + public Flux findAll() { + return groupCourseTypeDao.findAll() + .map(converter::toGroupCourseType); + } + + @Override + public Flux findAll(boolean includeDeleted) { + if (includeDeleted) { + return groupCourseTypeDao.findAll() + .map(converter::toGroupCourseType); + } else { + return groupCourseTypeDao.findAllByDeletedAtIsNull() + .map(converter::toGroupCourseType); + } + } + + @Override + public Flux findByKeyword(String keyword) { + if (keyword == null || keyword.isEmpty()) { + return findAll(false); + } + return groupCourseTypeDao.findByTypeNameContainingAndDeletedAtIsNull(keyword) + .map(converter::toGroupCourseType); + } + + @Override + public Flux findByCategory(String category) { + if (category == null || category.isEmpty()) { + return findAll(false); + } + return groupCourseTypeDao.findByCategoryAndDeletedAtIsNull(category) + .map(converter::toGroupCourseType); + } + + @Override + public Flux findByCategoryAndKeyword(String category, String keyword) { + Flux result; + + if (category != null && !category.isEmpty()) { + result = findByCategory(category); + } else { + result = findAll(false); + } + + if (keyword != null && !keyword.isEmpty()) { + result = result.filter(type -> type.getTypeName() != null && + type.getTypeName().toLowerCase().contains(keyword.toLowerCase())); + } + + return result; + } + + @Override + public Mono findByTypeName(String typeName) { + return groupCourseTypeDao.findByTypeNameAndDeletedAtIsNull(typeName) + .map(converter::toGroupCourseType); + } + + @Override + public Mono save(GroupCourseType groupCourseType) { + GroupCourseTypeEntity entity = converter.toGroupCourseTypeEntity(groupCourseType); + return groupCourseTypeDao.save(entity) + .map(converter::toGroupCourseType); + } + + @Override + public Mono update(GroupCourseType groupCourseType) { + return groupCourseTypeDao.findByIdIsAndDeletedAtIsNull(groupCourseType.getId()) + .switchIfEmpty(Mono.error(new RuntimeException("团课类型不存在"))) + .flatMap(existing -> { + existing.markNotNew(); + if (groupCourseType.getTypeName() != null) { + existing.setTypeName(groupCourseType.getTypeName()); + } + if (groupCourseType.getBaseDifficulty() != null) { + existing.setBaseDifficulty(groupCourseType.getBaseDifficulty()); + } + if (groupCourseType.getDescription() != null) { + existing.setDescription(groupCourseType.getDescription()); + } + if (groupCourseType.getCategory() != null) { + existing.setCategory(groupCourseType.getCategory()); + } + existing.setUpdatedAt(LocalDateTime.now()); + return groupCourseTypeDao.save(existing); + }) + .map(converter::toGroupCourseType); + } + + @Override + public Mono deleteById(Long id) { + return groupCourseTypeDao.softDelete(id, LocalDateTime.now()) + .then(); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/ICourseLabelService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/ICourseLabelService.java new file mode 100644 index 0000000..038dfc9 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/ICourseLabelService.java @@ -0,0 +1,30 @@ +package cn.novalon.gym.manage.groupcourse.service; + +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface ICourseLabelService { + + Mono findById(Long id); + + Flux findAll(); + + Flux findByKeyword(String keyword); + + Mono create(CourseLabel courseLabel); + + Mono update(Long id, CourseLabel courseLabel); + + Mono delete(Long id); + + Flux findByTypeId(Long typeId); + + Mono addLabelsToType(Long typeId, List labelIds); + + Mono removeLabelFromType(Long typeId, Long labelId); + + Mono clearLabelsFromType(Long typeId); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java index 7db154d..c312155 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java @@ -4,11 +4,13 @@ package cn.novalon.gym.manage.groupcourse.service; import cn.novalon.gym.manage.common.dto.PageRequest; import cn.novalon.gym.manage.common.dto.PageResponse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface IGroupCourseService { Mono findById(Long id); + Mono findDetailById(Long id); Flux findAll(); Flux findAll(boolean includeDeleted); diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseTypeService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseTypeService.java new file mode 100644 index 0000000..3e8a921 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseTypeService.java @@ -0,0 +1,32 @@ +package cn.novalon.gym.manage.groupcourse.service; + +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface IGroupCourseTypeService { + + Mono findById(Long id); + + Flux findAll(); + + Flux findAll(boolean includeDeleted); + + Flux findByKeyword(String keyword); + + Flux findByCategory(String category); + + Flux findByCategoryAndKeyword(String category, String keyword); + + Mono create(GroupCourseType groupCourseType); + + Mono update(Long id, GroupCourseType groupCourseType); + + Mono delete(Long id); + + /** + * 获取分类列表(去重) + * @return 分类名称列表 + */ + Flux findCategories(); +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java new file mode 100644 index 0000000..cb43eeb --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java @@ -0,0 +1,112 @@ +package cn.novalon.gym.manage.groupcourse.service.impl; + +import cn.novalon.gym.manage.common.util.RedisUtil; +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; +import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; +import cn.novalon.gym.manage.groupcourse.service.ICourseLabelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Service +public class CourseLabelService implements ICourseLabelService { + + private static final Logger logger = LoggerFactory.getLogger(CourseLabelService.class); + private static final String CACHE_KEY_DETAIL_PREFIX = "group_course:detail:"; + + private final ICourseLabelRepository courseLabelRepository; + private final IGroupCourseRepository groupCourseRepository; + private final RedisUtil redisUtil; + + public CourseLabelService(ICourseLabelRepository courseLabelRepository, + IGroupCourseRepository groupCourseRepository, + RedisUtil redisUtil) { + this.courseLabelRepository = courseLabelRepository; + this.groupCourseRepository = groupCourseRepository; + this.redisUtil = redisUtil; + } + + private Mono invalidateGroupCourseDetailCache(Long typeId) { + return groupCourseRepository.findByCourseType(typeId) + .flatMap(course -> { + String cacheKey = CACHE_KEY_DETAIL_PREFIX + course.getId(); + return redisUtil.delete(cacheKey) + .doOnSuccess(deleted -> logger.debug("清除团课详情缓存 - courseId={}", course.getId())); + }) + .then(); + } + + @Override + public Mono findById(Long id) { + return courseLabelRepository.findById(id); + } + + @Override + public Flux findAll() { + return courseLabelRepository.findAll(); + } + + @Override + public Flux findByKeyword(String keyword) { + return courseLabelRepository.findByKeyword(keyword); + } + + @Override + public Mono create(CourseLabel courseLabel) { + return courseLabelRepository.findByLabelName(courseLabel.getLabelName()) + .flatMap(existing -> Mono.error(new RuntimeException("标签名称已存在"))) + .switchIfEmpty(courseLabelRepository.save(courseLabel)) + .doOnSuccess(label -> logger.info("标签创建成功 - id={}, name={}", label.getId(), label.getLabelName())) + .doOnError(error -> logger.error("标签创建失败 - error: {}", error.getMessage())); + } + + @Override + public Mono update(Long id, CourseLabel courseLabel) { + courseLabel.setId(id); + return courseLabelRepository.update(courseLabel) + .doOnSuccess(label -> logger.info("标签更新成功 - id={}", id)) + .doOnError(error -> logger.error("标签更新失败 - id={}, error: {}", id, error.getMessage())); + } + + @Override + public Mono delete(Long id) { + return courseLabelRepository.deleteById(id) + .doOnSuccess(v -> logger.info("标签删除成功 - id={}", id)) + .doOnError(error -> logger.error("标签删除失败 - id={}, error: {}", id, error.getMessage())); + } + + @Override + public Flux findByTypeId(Long typeId) { + return courseLabelRepository.findByTypeId(typeId); + } + + @Override + public Mono addLabelsToType(Long typeId, List labelIds) { + return courseLabelRepository.addLabelsToType(typeId, labelIds) + .then(invalidateGroupCourseDetailCache(typeId)) + .doOnSuccess(v -> logger.info("标签添加到类型成功 - typeId={}, labelIds={}", typeId, labelIds)) + .doOnError(error -> logger.error("标签添加到类型失败 - typeId={}, error: {}", typeId, error.getMessage())); + } + + @Override + public Mono removeLabelFromType(Long typeId, Long labelId) { + return courseLabelRepository.removeLabelFromType(typeId, labelId) + .then(invalidateGroupCourseDetailCache(typeId)) + .doOnSuccess(v -> logger.info("从类型移除标签成功 - typeId={}, labelId={}", typeId, labelId)) + .doOnError(error -> logger.error("从类型移除标签失败 - typeId={}, labelId={}, error: {}", typeId, labelId, error.getMessage())); + } + + @Override + public Mono clearLabelsFromType(Long typeId) { + return courseLabelRepository.clearLabelsFromType(typeId) + .then(invalidateGroupCourseDetailCache(typeId)) + .doOnSuccess(v -> logger.info("清空类型标签成功 - typeId={}", typeId)) + .doOnError(error -> logger.error("清空类型标签失败 - typeId={}, error: {}", typeId, error.getMessage())); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java index 7847c9a..3922ce6 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java @@ -4,13 +4,18 @@ package cn.novalon.gym.manage.groupcourse.service.impl; import cn.novalon.gym.manage.common.dto.PageRequest; import cn.novalon.gym.manage.common.dto.PageResponse; import cn.novalon.gym.manage.common.util.RedisUtil; +import cn.novalon.gym.manage.groupcourse.domain.CourseLabel; import cn.novalon.gym.manage.groupcourse.domain.GroupCourse; import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail; +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; import cn.novalon.gym.manage.groupcourse.enums.CourseEvent; import cn.novalon.gym.manage.groupcourse.enums.CourseStatus; import cn.novalon.gym.manage.groupcourse.handler.GroupCourseStateMachine; +import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository; import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository; import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository; import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService; import cn.novalon.gym.manage.member.entity.MemberCard; import cn.novalon.gym.manage.member.entity.MemberCardRecord; @@ -33,6 +38,8 @@ public class GroupCourseService implements IGroupCourseService { private final IGroupCourseRepository groupCourseRepository; private final IGroupCourseBookingRepository bookingRepository; + private final IGroupCourseTypeRepository groupCourseTypeRepository; + private final ICourseLabelRepository courseLabelRepository; private final IMemberCardRecordService memberCardRecordService; private final MemberCardRepository memberCardRepository; private final RedisUtil redisUtil; @@ -41,12 +48,15 @@ public class GroupCourseService implements IGroupCourseService { private static final String CACHE_KEY_PREFIX = "group_course:page:"; private static final String CACHE_KEY_ID_PREFIX = "group_course:id:"; + private static final String CACHE_KEY_DETAIL_PREFIX = "group_course:detail:"; private static final long CACHE_EXPIRE_SECONDS = 300; private static final double DEFAULT_GROUP_COURSE_PRICE = 50.0; public GroupCourseService(IGroupCourseRepository groupCourseRepository, IGroupCourseBookingRepository bookingRepository, + IGroupCourseTypeRepository groupCourseTypeRepository, + ICourseLabelRepository courseLabelRepository, IMemberCardRecordService memberCardRecordService, MemberCardRepository memberCardRepository, RedisUtil redisUtil, @@ -54,6 +64,8 @@ public class GroupCourseService implements IGroupCourseService { GroupCourseStateMachine stateMachine){ this.groupCourseRepository = groupCourseRepository; this.bookingRepository = bookingRepository; + this.groupCourseTypeRepository = groupCourseTypeRepository; + this.courseLabelRepository = courseLabelRepository; this.memberCardRecordService = memberCardRecordService; this.memberCardRepository = memberCardRepository; this.redisUtil = redisUtil; @@ -61,6 +73,93 @@ public class GroupCourseService implements IGroupCourseService { this.stateMachine = stateMachine; } + @Override + public Mono findDetailById(Long id) { + String cacheKey = CACHE_KEY_DETAIL_PREFIX + id; + + return redisUtil.get(cacheKey, String.class) + .flatMap(cachedJson -> { + if (cachedJson != null) { + try { + GroupCourseDetail detail = objectMapper.readValue(cachedJson, GroupCourseDetail.class); + logger.info("缓存命中 - findDetailById: id={}", id); + return Mono.just(detail); + } catch (JsonProcessingException e) { + logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage()); + return redisUtil.delete(cacheKey).then(Mono.empty()); + } + } + return Mono.empty(); + }) + .switchIfEmpty( + groupCourseRepository.findByIdAndDeletedAtIsNull(id) + .flatMap(course -> { + // 查询类型信息 + Long courseTypeId = course.getCourseType(); + + if (courseTypeId == null) { + // 没有类型,直接构建详情 + return Mono.just(buildDetail(course, null)); + } + + // 有类型,查询类型信息 + return groupCourseTypeRepository.findById(courseTypeId) + .flatMap(type -> { + // 查询标签 + return courseLabelRepository.findByTypeId(type.getId()) + .collectList() + .map(labels -> { + type.setLabels(labels); + return buildDetail(course, type); + }); + }) + .switchIfEmpty(Mono.just(buildDetail(course, null))); + }) + .flatMap(detail -> { + try { + String jsonData = objectMapper.writeValueAsString(detail); + return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS) + .thenReturn(detail) + .doOnSuccess(d -> logger.debug("缓存已设置 - findDetailById: id={}", id)); + } catch (JsonProcessingException e) { + logger.error("缓存设置失败 - id: {}, error: {}", id, e.getMessage()); + return Mono.just(detail); + } + }) + .doOnSubscribe(sub -> logger.debug("缓存未命中,查询数据库 - findDetailById: id={}", id)) + ); + } + + /** + * 构建团课完整信息对象 + */ + private GroupCourseDetail buildDetail(GroupCourse course, GroupCourseType type) { + GroupCourseDetail detail = new GroupCourseDetail(); + detail.setId(course.getId()); + detail.setCourseName(course.getCourseName()); + detail.setCoachId(course.getCoachId()); + detail.setCourseType(course.getCourseType()); + detail.setStartTime(course.getStartTime()); + detail.setEndTime(course.getEndTime()); + detail.setMaxMembers(course.getMaxMembers()); + detail.setCurrentMembers(course.getCurrentMembers()); + detail.setStatus(course.getStatus()); + detail.setLocation(course.getLocation()); + detail.setCoverImage(course.getCoverImage()); + detail.setDescription(course.getDescription()); + detail.setPointCardAmount(course.getPointCardAmount()); + detail.setStoredValueAmount(course.getStoredValueAmount()); + detail.setCreatedAt(course.getCreatedAt()); + detail.setUpdatedAt(course.getUpdatedAt()); + + // 设置类型信息 + if (type != null) { + detail.setTypeInfo(type); + } + + return detail; + } + @Override public Mono findById(Long id) { String cacheKey = CACHE_KEY_ID_PREFIX + id; @@ -391,6 +490,7 @@ public class GroupCourseService implements IGroupCourseService { private Mono clearCache() { return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*") - .then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*")).then(); + .then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*")) + .then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*")).then(); } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseTypeService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseTypeService.java new file mode 100644 index 0000000..67ec080 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseTypeService.java @@ -0,0 +1,86 @@ +package cn.novalon.gym.manage.groupcourse.service.impl; + +import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType; +import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository; +import cn.novalon.gym.manage.groupcourse.service.IGroupCourseTypeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.HashSet; +import java.util.Set; + +@Service +public class GroupCourseTypeService implements IGroupCourseTypeService { + + private static final Logger logger = LoggerFactory.getLogger(GroupCourseTypeService.class); + + private final IGroupCourseTypeRepository groupCourseTypeRepository; + + public GroupCourseTypeService(IGroupCourseTypeRepository groupCourseTypeRepository) { + this.groupCourseTypeRepository = groupCourseTypeRepository; + } + + @Override + public Mono findById(Long id) { + return groupCourseTypeRepository.findById(id); + } + + @Override + public Flux findAll() { + return groupCourseTypeRepository.findAll(false); + } + + @Override + public Flux findAll(boolean includeDeleted) { + return groupCourseTypeRepository.findAll(includeDeleted); + } + + @Override + public Flux findByKeyword(String keyword) { + return groupCourseTypeRepository.findByKeyword(keyword); + } + + @Override + public Flux findByCategory(String category) { + return groupCourseTypeRepository.findByCategory(category); + } + + @Override + public Flux findByCategoryAndKeyword(String category, String keyword) { + return groupCourseTypeRepository.findByCategoryAndKeyword(category, keyword); + } + + @Override + public Mono create(GroupCourseType groupCourseType) { + return groupCourseTypeRepository.findByTypeName(groupCourseType.getTypeName()) + .flatMap(existing -> Mono.error(new RuntimeException("团课类型名称已存在"))) + .switchIfEmpty(groupCourseTypeRepository.save(groupCourseType)) + .doOnSuccess(type -> logger.info("团课类型创建成功 - id={}, name={}", type.getId(), type.getTypeName())) + .doOnError(error -> logger.error("团课类型创建失败 - error: {}", error.getMessage())); + } + + @Override + public Mono update(Long id, GroupCourseType groupCourseType) { + return groupCourseTypeRepository.update(groupCourseType) + .doOnSuccess(type -> logger.info("团课类型更新成功 - id={}", id)) + .doOnError(error -> logger.error("团课类型更新失败 - id={}, error: {}", id, error.getMessage())); + } + + @Override + public Mono delete(Long id) { + return groupCourseTypeRepository.deleteById(id) + .doOnSuccess(v -> logger.info("团课类型删除成功 - id={}", id)) + .doOnError(error -> logger.error("团课类型删除失败 - id={}, error: {}", id, error.getMessage())); + } + + @Override + public Flux findCategories() { + return groupCourseTypeRepository.findAll(false) + .map(GroupCourseType::getCategory) + .filter(category -> category != null && !category.isEmpty()) + .distinct(); + } +} \ No newline at end of file diff --git a/gym-manage-api/log.text b/gym-manage-api/log.text index 289fce2..081e809 100644 --- a/gym-manage-api/log.text +++ b/gym-manage-api/log.text @@ -1,152 +1,223 @@ -D:\JAVA\jdk-21\bin\java.exe -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true "-Dmanagement.endpoints.jmx.exposure.include=*" "-javaagent:D:\IDEA2025\IntelliJ IDEA 2025.3.3\lib\idea_rt.jar=61856" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-app\target\classes;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-sys\target\classes;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-common\target\classes;D:\Maven\JARs\io\jsonwebtoken\jjwt-api\0.11.5\jjwt-api-0.11.5.jar;D:\Maven\JARs\io\jsonwebtoken\jjwt-impl\0.11.5\jjwt-impl-0.11.5.jar;D:\Maven\JARs\io\jsonwebtoken\jjwt-jackson\0.11.5\jjwt-jackson-0.11.5.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-security\3.5.13\spring-boot-starter-security-3.5.13.jar;D:\Maven\JARs\org\springframework\security\spring-security-config\6.2.4\spring-security-config-6.2.4.jar;D:\Maven\JARs\org\springframework\data\spring-data-commons\3.5.10\spring-data-commons-3.5.10.jar;D:\Maven\JARs\org\springframework\spring-beans\6.2.17\spring-beans-6.2.17.jar;D:\Maven\JARs\org\apache\poi\poi\5.2.5\poi-5.2.5.jar;D:\Maven\JARs\commons-codec\commons-codec\1.18.0\commons-codec-1.18.0.jar;D:\Maven\JARs\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;D:\Maven\JARs\commons-io\commons-io\2.15.0\commons-io-2.15.0.jar;D:\Maven\JARs\com\zaxxer\SparseBitSet\1.3\SparseBitSet-1.3.jar;D:\Maven\JARs\org\apache\logging\log4j\log4j-api\2.24.3\log4j-api-2.24.3.jar;D:\Maven\JARs\org\apache\poi\poi-ooxml\5.2.5\poi-ooxml-5.2.5.jar;D:\Maven\JARs\org\apache\poi\poi-ooxml-lite\5.2.5\poi-ooxml-lite-5.2.5.jar;D:\Maven\JARs\org\apache\xmlbeans\xmlbeans\5.2.0\xmlbeans-5.2.0.jar;D:\Maven\JARs\com\github\virtuald\curvesapi\1.08\curvesapi-1.08.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-notify\target\classes;D:\Maven\JARs\com\fasterxml\jackson\core\jackson-databind\2.21.2\jackson-databind-2.21.2.jar;D:\Maven\JARs\com\fasterxml\jackson\core\jackson-annotations\2.21\jackson-annotations-2.21.jar;D:\Maven\JARs\com\fasterxml\jackson\core\jackson-core\2.21.2\jackson-core-2.21.2.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-file\target\classes;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-db\target\classes;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-data-r2dbc\3.5.13\spring-boot-starter-data-r2dbc-3.5.13.jar;D:\Maven\JARs\io\r2dbc\r2dbc-pool\1.0.2.RELEASE\r2dbc-pool-1.0.2.RELEASE.jar;D:\Maven\JARs\io\projectreactor\addons\reactor-pool\1.1.8\reactor-pool-1.1.8.jar;D:\Maven\JARs\org\springframework\data\spring-data-r2dbc\3.5.10\spring-data-r2dbc-3.5.10.jar;D:\Maven\JARs\org\springframework\data\spring-data-relational\3.5.10\spring-data-relational-3.5.10.jar;D:\Maven\JARs\org\springframework\spring-tx\6.2.17\spring-tx-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-context\6.2.17\spring-context-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-r2dbc\6.2.17\spring-r2dbc-6.2.17.jar;D:\Maven\JARs\org\mapstruct\mapstruct\1.5.5.Final\mapstruct-1.5.5.Final.jar;D:\Maven\JARs\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;D:\Maven\JARs\org\apache\commons\commons-lang3\3.17.0\commons-lang3-3.17.0.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\gym-member\target\classes;D:\Maven\JARs\com\github\binarywang\weixin-java-miniapp\4.6.0\weixin-java-miniapp-4.6.0.jar;D:\Maven\JARs\com\github\binarywang\weixin-java-common\4.6.0\weixin-java-common-4.6.0.jar;D:\Maven\JARs\com\thoughtworks\xstream\xstream\1.4.20\xstream-1.4.20.jar;D:\Maven\JARs\io\github\x-stream\mxparser\1.2.2\mxparser-1.2.2.jar;D:\Maven\JARs\xmlpull\xmlpull\1.1.3.1\xmlpull-1.1.3.1.jar;D:\Maven\JARs\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;D:\Maven\JARs\org\apache\httpcomponents\httpcore\4.4.16\httpcore-4.4.16.jar;D:\Maven\JARs\org\apache\httpcomponents\httpmime\4.5.13\httpmime-4.5.13.jar;D:\Maven\JARs\org\slf4j\jcl-over-slf4j\2.0.17\jcl-over-slf4j-2.0.17.jar;D:\Maven\JARs\com\google\code\gson\gson\2.13.2\gson-2.13.2.jar;D:\Maven\JARs\com\google\errorprone\error_prone_annotations\2.41.0\error_prone_annotations-2.41.0.jar;D:\Maven\JARs\com\google\guava\guava\33.3.1-jre\guava-33.3.1-jre.jar;D:\Maven\JARs\com\google\guava\failureaccess\1.0.2\failureaccess-1.0.2.jar;D:\Maven\JARs\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;D:\Maven\JARs\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;D:\Maven\JARs\com\google\j2objc\j2objc-annotations\3.0.0\j2objc-annotations-3.0.0.jar;D:\Maven\JARs\org\dom4j\dom4j\2.1.3\dom4j-2.1.3.jar;D:\Maven\JARs\org\bouncycastle\bcpkix-jdk15on\1.70\bcpkix-jdk15on-1.70.jar;D:\Maven\JARs\org\bouncycastle\bcprov-jdk15on\1.70\bcprov-jdk15on-1.70.jar;D:\Maven\JARs\org\bouncycastle\bcutil-jdk15on\1.70\bcutil-jdk15on-1.70.jar;D:\Maven\JARs\com\github\binarywang\weixin-java-mp\4.6.0\weixin-java-mp-4.6.0.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-data-elasticsearch\3.5.13\spring-boot-starter-data-elasticsearch-3.5.13.jar;D:\Maven\JARs\org\springframework\data\spring-data-elasticsearch\5.5.10\spring-data-elasticsearch-5.5.10.jar;D:\Maven\JARs\co\elastic\clients\elasticsearch-java\8.18.8\elasticsearch-java-8.18.8.jar;D:\Maven\JARs\jakarta\json\jakarta.json-api\2.1.3\jakarta.json-api-2.1.3.jar;D:\Maven\JARs\org\eclipse\parsson\parsson\1.0.5\parsson-1.0.5.jar;D:\Maven\JARs\io\opentelemetry\opentelemetry-api\1.49.0\opentelemetry-api-1.49.0.jar;D:\Maven\JARs\io\opentelemetry\opentelemetry-context\1.49.0\opentelemetry-context-1.49.0.jar;D:\Maven\JARs\org\elasticsearch\client\elasticsearch-rest-client\8.18.8\elasticsearch-rest-client-8.18.8.jar;D:\Maven\JARs\org\apache\httpcomponents\httpasyncclient\4.1.5\httpasyncclient-4.1.5.jar;D:\Maven\JARs\org\apache\httpcomponents\httpcore-nio\4.4.16\httpcore-nio-4.4.16.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\gym-checkIn\target\classes;D:\Maven\JARs\com\google\zxing\core\3.5.1\core-3.5.1.jar;D:\Maven\JARs\com\google\zxing\javase\3.5.1\javase-3.5.1.jar;D:\Maven\JARs\com\beust\jcommander\1.82\jcommander-1.82.jar;D:\Maven\JARs\com\github\jai-imageio\jai-imageio-core\1.4.0\jai-imageio-core-1.4.0.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-websocket\3.5.13\spring-boot-starter-websocket-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-web\3.5.13\spring-boot-starter-web-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-tomcat\3.5.13\spring-boot-starter-tomcat-3.5.13.jar;D:\Maven\JARs\org\apache\tomcat\embed\tomcat-embed-core\10.1.53\tomcat-embed-core-10.1.53.jar;D:\Maven\JARs\org\apache\tomcat\embed\tomcat-embed-websocket\10.1.53\tomcat-embed-websocket-10.1.53.jar;D:\Maven\JARs\org\springframework\spring-webmvc\6.2.17\spring-webmvc-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-messaging\6.2.17\spring-messaging-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-websocket\6.2.17\spring-websocket-6.2.17.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\gym-dataCount\target\classes;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-validation\3.5.13\spring-boot-starter-validation-3.5.13.jar;D:\Maven\JARs\org\apache\tomcat\embed\tomcat-embed-el\10.1.53\tomcat-embed-el-10.1.53.jar;D:\Maven\JARs\org\hibernate\validator\hibernate-validator\8.0.3.Final\hibernate-validator-8.0.3.Final.jar;D:\Maven\JARs\jakarta\validation\jakarta.validation-api\3.0.2\jakarta.validation-api-3.0.2.jar;D:\Maven\JARs\org\jboss\logging\jboss-logging\3.6.3.Final\jboss-logging-3.6.3.Final.jar;D:\Maven\JARs\com\fasterxml\classmate\1.7.3\classmate-1.7.3.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-webflux\3.5.13\spring-boot-starter-webflux-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter\3.5.13\spring-boot-starter-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot\3.5.13\spring-boot-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-autoconfigure\3.5.13\spring-boot-autoconfigure-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-logging\3.5.13\spring-boot-starter-logging-3.5.13.jar;D:\Maven\JARs\ch\qos\logback\logback-classic\1.5.32\logback-classic-1.5.32.jar;D:\Maven\JARs\ch\qos\logback\logback-core\1.5.32\logback-core-1.5.32.jar;D:\Maven\JARs\org\apache\logging\log4j\log4j-to-slf4j\2.24.3\log4j-to-slf4j-2.24.3.jar;D:\Maven\JARs\org\slf4j\jul-to-slf4j\2.0.17\jul-to-slf4j-2.0.17.jar;D:\Maven\JARs\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;D:\Maven\JARs\org\yaml\snakeyaml\2.4\snakeyaml-2.4.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-json\3.5.13\spring-boot-starter-json-3.5.13.jar;D:\Maven\JARs\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.21.2\jackson-datatype-jdk8-2.21.2.jar;D:\Maven\JARs\com\fasterxml\jackson\module\jackson-module-parameter-names\2.21.2\jackson-module-parameter-names-2.21.2.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-reactor-netty\3.5.13\spring-boot-starter-reactor-netty-3.5.13.jar;D:\Maven\JARs\io\projectreactor\netty\reactor-netty-http\1.2.16\reactor-netty-http-1.2.16.jar;D:\Maven\JARs\io\netty\netty-codec-http\4.1.132.Final\netty-codec-http-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-codec-http2\4.1.132.Final\netty-codec-http2-4.1.132.Final.jar;D:\Maven\JARs\org\springframework\spring-web\6.2.17\spring-web-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-webflux\6.2.17\spring-webflux-6.2.17.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-aop\3.5.13\spring-boot-starter-aop-3.5.13.jar;D:\Maven\JARs\org\springframework\spring-aop\6.2.17\spring-aop-6.2.17.jar;D:\Maven\JARs\org\aspectj\aspectjweaver\1.9.25.1\aspectjweaver-1.9.25.1.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-actuator\3.5.13\spring-boot-starter-actuator-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-actuator-autoconfigure\3.5.13\spring-boot-actuator-autoconfigure-3.5.13.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-actuator\3.5.13\spring-boot-actuator-3.5.13.jar;D:\Maven\JARs\io\micrometer\micrometer-observation\1.15.10\micrometer-observation-1.15.10.jar;D:\Maven\JARs\io\micrometer\micrometer-commons\1.15.10\micrometer-commons-1.15.10.jar;D:\Maven\JARs\io\micrometer\micrometer-jakarta9\1.15.10\micrometer-jakarta9-1.15.10.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-spring-boot3\2.4.0\resilience4j-spring-boot3-2.4.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-spring6\2.4.0\resilience4j-spring6-2.4.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-annotations\2.2.0\resilience4j-annotations-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-consumer\2.2.0\resilience4j-consumer-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-circularbuffer\2.2.0\resilience4j-circularbuffer-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-framework-common\2.4.0\resilience4j-framework-common-2.4.0.jar;D:\Maven\JARs\org\slf4j\slf4j-api\2.0.17\slf4j-api-2.0.17.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-micrometer\2.2.0\resilience4j-micrometer-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-reactor\2.4.0\resilience4j-reactor-2.4.0.jar;D:\Maven\JARs\io\projectreactor\reactor-core\3.7.17\reactor-core-3.7.17.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-circuitbreaker\2.2.0\resilience4j-circuitbreaker-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-core\2.2.0\resilience4j-core-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-ratelimiter\2.2.0\resilience4j-ratelimiter-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-timelimiter\2.2.0\resilience4j-timelimiter-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-bulkhead\2.2.0\resilience4j-bulkhead-2.2.0.jar;D:\Maven\JARs\io\github\resilience4j\resilience4j-retry\2.2.0\resilience4j-retry-2.2.0.jar;D:\Maven\JARs\io\reactivex\rxjava3\rxjava\3.1.9\rxjava-3.1.9.jar;D:\Maven\JARs\org\reactivestreams\reactive-streams\1.0.4\reactive-streams-1.0.4.jar;D:\Maven\JARs\io\micrometer\micrometer-registry-prometheus\1.13.4\micrometer-registry-prometheus-1.13.4.jar;D:\Maven\JARs\io\micrometer\micrometer-core\1.15.10\micrometer-core-1.15.10.jar;D:\Maven\JARs\org\hdrhistogram\HdrHistogram\2.2.2\HdrHistogram-2.2.2.jar;D:\Maven\JARs\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-core\1.3.10\prometheus-metrics-core-1.3.10.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-model\1.3.10\prometheus-metrics-model-1.3.10.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-config\1.3.10\prometheus-metrics-config-1.3.10.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-tracer-common\1.3.10\prometheus-metrics-tracer-common-1.3.10.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-exposition-formats\1.3.10\prometheus-metrics-exposition-formats-1.3.10.jar;D:\Maven\JARs\io\prometheus\prometheus-metrics-exposition-textformats\1.3.10\prometheus-metrics-exposition-textformats-1.3.10.jar;D:\Maven\JARs\org\postgresql\r2dbc-postgresql\1.0.0.RELEASE\r2dbc-postgresql-1.0.0.RELEASE.jar;D:\Maven\JARs\io\r2dbc\r2dbc-spi\1.0.0.RELEASE\r2dbc-spi-1.0.0.RELEASE.jar;D:\Maven\JARs\com\ongres\scram\client\2.1\client-2.1.jar;D:\Maven\JARs\com\ongres\scram\common\2.1\common-2.1.jar;D:\Maven\JARs\com\ongres\stringprep\saslprep\1.1\saslprep-1.1.jar;D:\Maven\JARs\com\ongres\stringprep\stringprep\1.1\stringprep-1.1.jar;D:\Maven\JARs\io\projectreactor\netty\reactor-netty-core\1.2.16\reactor-netty-core-1.2.16.jar;D:\Maven\JARs\io\netty\netty-handler\4.1.132.Final\netty-handler-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-resolver\4.1.132.Final\netty-resolver-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-buffer\4.1.132.Final\netty-buffer-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-transport-native-unix-common\4.1.132.Final\netty-transport-native-unix-common-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-codec\4.1.132.Final\netty-codec-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-handler-proxy\4.1.132.Final\netty-handler-proxy-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-codec-socks\4.1.132.Final\netty-codec-socks-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-resolver-dns\4.1.132.Final\netty-resolver-dns-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-codec-dns\4.1.132.Final\netty-codec-dns-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-resolver-dns-native-macos\4.1.132.Final\netty-resolver-dns-native-macos-4.1.132.Final-osx-x86_64.jar;D:\Maven\JARs\io\netty\netty-resolver-dns-classes-macos\4.1.132.Final\netty-resolver-dns-classes-macos-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-transport-native-epoll\4.1.132.Final\netty-transport-native-epoll-4.1.132.Final-linux-x86_64.jar;D:\Maven\JARs\io\netty\netty-transport-classes-epoll\4.1.132.Final\netty-transport-classes-epoll-4.1.132.Final.jar;D:\Maven\JARs\org\postgresql\postgresql\42.7.4\postgresql-42.7.4.jar;D:\Maven\JARs\org\checkerframework\checker-qual\3.42.0\checker-qual-3.42.0.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-jdbc\3.5.13\spring-boot-starter-jdbc-3.5.13.jar;D:\Maven\JARs\com\zaxxer\HikariCP\6.3.3\HikariCP-6.3.3.jar;D:\Maven\JARs\org\springframework\spring-jdbc\6.2.17\spring-jdbc-6.2.17.jar;D:\Maven\JARs\com\h2database\h2\2.3.232\h2-2.3.232.jar;D:\Maven\JARs\io\r2dbc\r2dbc-h2\1.0.1.RELEASE\r2dbc-h2-1.0.1.RELEASE.jar;D:\Maven\JARs\org\flywaydb\flyway-core\11.0.1\flyway-core-11.0.1.jar;D:\Maven\JARs\com\fasterxml\jackson\dataformat\jackson-dataformat-toml\2.21.2\jackson-dataformat-toml-2.21.2.jar;D:\Maven\JARs\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.21.2\jackson-datatype-jsr310-2.21.2.jar;D:\Maven\JARs\org\flywaydb\flyway-database-postgresql\11.0.1\flyway-database-postgresql-11.0.1.jar;D:\Maven\JARs\jakarta\xml\bind\jakarta.xml.bind-api\4.0.4\jakarta.xml.bind-api-4.0.4.jar;D:\Maven\JARs\jakarta\activation\jakarta.activation-api\2.1.4\jakarta.activation-api-2.1.4.jar;D:\Maven\JARs\org\springframework\spring-core\6.2.17\spring-core-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-jcl\6.2.17\spring-jcl-6.2.17.jar;D:\Maven\JARs\org\springframework\security\spring-security-core\6.5.9\spring-security-core-6.5.9.jar;D:\Maven\JARs\org\springframework\security\spring-security-crypto\6.2.4\spring-security-crypto-6.2.4.jar;D:\Maven\JARs\org\springframework\spring-expression\6.2.17\spring-expression-6.2.17.jar;D:\Maven\JARs\org\springframework\security\spring-security-web\6.5.9\spring-security-web-6.5.9.jar;D:\Maven\JARs\org\apache\commons\commons-compress\1.24.0\commons-compress-1.24.0.jar;D:\Maven\JARs\org\springdoc\springdoc-openapi-starter-webflux-ui\2.8.16\springdoc-openapi-starter-webflux-ui-2.8.16.jar;D:\Maven\JARs\org\springdoc\springdoc-openapi-starter-webflux-api\2.8.16\springdoc-openapi-starter-webflux-api-2.8.16.jar;D:\Maven\JARs\org\springdoc\springdoc-openapi-starter-common\2.8.16\springdoc-openapi-starter-common-2.8.16.jar;D:\Maven\JARs\io\swagger\core\v3\swagger-core-jakarta\2.2.43\swagger-core-jakarta-2.2.43.jar;D:\Maven\JARs\io\swagger\core\v3\swagger-models-jakarta\2.2.43\swagger-models-jakarta-2.2.43.jar;D:\Maven\JARs\com\fasterxml\jackson\dataformat\jackson-dataformat-yaml\2.21.2\jackson-dataformat-yaml-2.21.2.jar;D:\Maven\JARs\org\webjars\swagger-ui\5.32.0\swagger-ui-5.32.0.jar;D:\Maven\JARs\org\webjars\webjars-locator-lite\1.1.3\webjars-locator-lite-1.1.3.jar;D:\Maven\JARs\org\jspecify\jspecify\1.0.0\jspecify-1.0.0.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-data-redis\3.5.13\spring-boot-starter-data-redis-3.5.13.jar;D:\Maven\JARs\io\lettuce\lettuce-core\6.6.0.RELEASE\lettuce-core-6.6.0.RELEASE.jar;D:\Maven\JARs\redis\clients\authentication\redis-authx-core\0.1.1-beta2\redis-authx-core-0.1.1-beta2.jar;D:\Maven\JARs\io\netty\netty-common\4.1.132.Final\netty-common-4.1.132.Final.jar;D:\Maven\JARs\io\netty\netty-transport\4.1.132.Final\netty-transport-4.1.132.Final.jar;D:\Maven\JARs\org\springframework\data\spring-data-redis\3.5.10\spring-data-redis-3.5.10.jar;D:\Maven\JARs\org\springframework\data\spring-data-keyvalue\3.5.10\spring-data-keyvalue-3.5.10.jar;D:\Maven\JARs\org\springframework\spring-oxm\6.2.17\spring-oxm-6.2.17.jar;D:\Maven\JARs\org\springframework\spring-context-support\6.2.17\spring-context-support-6.2.17.jar;D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\gym-groupCourse\target\classes;D:\Maven\JARs\io\swagger\core\v3\swagger-annotations-jakarta\2.2.43\swagger-annotations-jakarta-2.2.43.jar;D:\Maven\JARs\org\projectlombok\lombok\1.18.30\lombok-1.18.30.jar;D:\Maven\JARs\cn\hutool\hutool-all\5.8.38\hutool-all-5.8.38.jar;D:\Maven\JARs\org\springframework\boot\spring-boot-starter-data-redis-reactive\3.5.13\spring-boot-starter-data-redis-reactive-3.5.13.jar cn.novalon.gym.manage.app.SimpleManageApplication -22:36:32.642 [main] INFO cn.novalon.gym.manage.app.SimpleManageApplication -- 简化版应用程序启动中... -22:36:32.645 [main] INFO cn.novalon.gym.manage.app.SimpleManageApplication -- 包扫描路径: cn.novalon.gym.manage.app -╔═══════════════════════════════════════════════════════════════════╗ -║ ║ -║ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██████╗ ███╗ ██╗ ║ -║ ████╗ ██║██╔═══██╗██║ ██║██╔══██╗██║ ██╔═══██╗████╗ ██║ ║ -║ ██╔██╗ ██║██║ ██║██║ ██║███████║██║ ██║ ██║██╔██╗ ██║ ║ -║ ██║╚██╗██║██║ ██║╚██╗ ██╔╝██╔══██║██║ ██║ ██║██║╚██╗██║ ║ -║ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║███████╗╚██████╔╝██║ ╚████║ ║ -║ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ║ -║ ║ -║ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗ ║ -║ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝ ║ -║ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ║ -║ ██║╚██╔╝██║██╔══██║██║╚██╗██║██╔══██║██║ ██║██╔══╝ ║ -║ ██║ ╚═╝ ██║██║ ██║██║ ╚████║██║ ██║╚██████╔╝███████╗ ║ -║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ║ -║ ║ -║ ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗ ║ -║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║ ║ -║ ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║ ║ -║ ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║ ║ -║ ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║ ║ -║ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║ -║ ║ -╚═══════════════════════════════════════════════════════════════════╝ - - :: Novalon Manage System :: - Version: Unknown - Spring Boot: 3.5.13 - Java: 21.0.9 - PID: 12240 - -2026-06-09T22:36:33.227+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.app.SimpleManageApplication : Starting SimpleManageApplication using Java 21.0.9 with PID 12240 (D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api\manage-app\target\classes started by 29827 in D:\Work\BIG_project\week2\base6-dataCount\gym-manage\gym-manage-api) -2026-06-09T22:36:33.228+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.app.SimpleManageApplication : The following 1 profile is active: "dev" -2026-06-09T22:36:34.524+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode -2026-06-09T22:36:34.525+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. -2026-06-09T22:36:34.812+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 281 ms. Found 27 R2DBC repository interfaces. -2026-06-09T22:36:34.813+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode -2026-06-09T22:36:34.814+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Reactive Elasticsearch repositories in DEFAULT mode. -2026-06-09T22:36:34.820+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 5 ms. Found 1 Reactive Elasticsearch repository interface. -2026-06-09T22:36:34.908+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode -2026-06-09T22:36:34.908+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. -2026-06-09T22:36:34.914+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 1 R2DBC repository interface. -2026-06-09T22:36:35.369+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode -2026-06-09T22:36:35.370+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Elasticsearch repositories in DEFAULT mode. -2026-06-09T22:36:35.378+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 Elasticsearch repository interfaces. -2026-06-09T22:36:35.396+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode -2026-06-09T22:36:35.397+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. -2026-06-09T22:36:35.405+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 1 ms. Found 0 Redis repository interfaces. -2026-06-09T22:36:36.168+08:00 INFO 12240 --- [gym-manage-api] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8084 (http) -2026-06-09T22:36:36.183+08:00 INFO 12240 --- [gym-manage-api] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] -2026-06-09T22:36:36.183+08:00 INFO 12240 --- [gym-manage-api] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.53] -2026-06-09T22:36:36.886+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.gym.manage.sys.config.AsyncConfig : 审计日志异步线程池初始化完成: corePoolSize=5, maxPoolSize=10, queueCapacity=100 -2026-06-09T22:36:36.913+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.gym.manage.sys.audit.AuditLogAspect : === AuditLogAspect 初始化完成 === -2026-06-09T22:36:37.031+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : === OperationLogWebFilter 构造函数被调用 === -2026-06-09T22:36:37.031+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : === OperationLogWebFilter 初始化 === -2026-06-09T22:36:37.031+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : 操作日志映射配置数量: 10 -2026-06-09T22:36:37.031+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/roles/ -> 角色管理:更新角色 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/users -> 用户管理:创建用户 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/roles -> 角色管理:创建角色 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/users/ -> 用户管理:删除用户 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/menus/ -> 菜单管理:更新菜单 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/menus/ -> 菜单管理:删除菜单 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/users/ -> 用户管理:用户操作 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/menus -> 菜单管理:创建菜单 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/roles/ -> 角色管理:删除角色 -2026-06-09T22:36:37.033+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/users/ -> 用户管理:更新用户 -2026-06-09T22:36:37.079+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.s.config.PasswordEncoderConfig : PasswordEncoderConfig 已加载 -2026-06-09T22:36:37.498+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.s.config.PasswordEncoderConfig : 创建主密码编码器: BCryptPasswordEncoder(strength=12), 类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -2026-06-09T22:36:37.501+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.s.c.service.impl.SysUserService : 使用的密码编码器类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -2026-06-09T22:36:37.552+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.s.h.a.PasswordDiagnosticHandler : PasswordDiagnosticHandler initialized with encoder: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -2026-06-09T22:36:37.562+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : SysAuthHandler使用的密码编码器类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -2026-06-09T22:36:37.890+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : DirectEncoder10测试: 密码=test123, 哈希=$2a$10$mSg, 前缀=$2a$10$ -2026-06-09T22:36:37.891+08:00 INFO 12240 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : DirectEncoder12测试: 密码=test123, 哈希=$2a$12$0BX, 前缀=$2a$12$ -2026-06-09T22:36:39.254+08:00 WARN 12240 --- [gym-manage-api] [ main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataStatisticsHandler': Unsatisfied dependency expressed through field 'dataStatisticsService': Error creating bean with name 'dataStatisticsServiceImpl': Unsatisfied dependency expressed through field 'dataStatisticsDao': Error creating bean with name 'dataStatisticsDao' defined in cn.novalon.gym.manage.datacount.dao.DataStatisticsDao defined in @EnableR2dbcRepositories declared on DataCountAutoConfiguration: Couldn't find PersistentEntity for type class java.lang.Object -2026-06-09T22:36:39.276+08:00 INFO 12240 --- [gym-manage-api] [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat] -2026-06-09T22:36:39.294+08:00 INFO 12240 --- [gym-manage-api] [ main] .s.b.a.l.ConditionEvaluationReportLogger : - -Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. -2026-06-09T22:36:39.310+08:00 ERROR 12240 --- [gym-manage-api] [ main] o.s.boot.SpringApplication : Application run failed - -org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataStatisticsHandler': Unsatisfied dependency expressed through field 'dataStatisticsService': Error creating bean with name 'dataStatisticsServiceImpl': Unsatisfied dependency expressed through field 'dataStatisticsDao': Error creating bean with name 'dataStatisticsDao' defined in cn.novalon.gym.manage.datacount.dao.DataStatisticsDao defined in @EnableR2dbcRepositories declared on DataCountAutoConfiguration: Couldn't find PersistentEntity for type class java.lang.Object - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1459) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1228) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1194) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1130) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:991) ~[spring-context-6.2.17.jar:6.2.17] - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:628) ~[spring-context-6.2.17.jar:6.2.17] - at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-3.5.13.jar:3.5.13] - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.5.13.jar:3.5.13] - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.13.jar:3.5.13] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.13.jar:3.5.13] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.5.13.jar:3.5.13] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.5.13.jar:3.5.13] - at cn.novalon.gym.manage.app.SimpleManageApplication.main(SimpleManageApplication.java:29) ~[classes/:na] -Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataStatisticsServiceImpl': Unsatisfied dependency expressed through field 'dataStatisticsDao': Error creating bean with name 'dataStatisticsDao' defined in cn.novalon.gym.manage.datacount.dao.DataStatisticsDao defined in @EnableR2dbcRepositories declared on DataCountAutoConfiguration: Couldn't find PersistentEntity for type class java.lang.Object - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1459) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1770) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1653) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.2.17.jar:6.2.17] - ... 22 common frames omitted -Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataStatisticsDao' defined in cn.novalon.gym.manage.datacount.dao.DataStatisticsDao defined in @EnableR2dbcRepositories declared on DataCountAutoConfiguration: Couldn't find PersistentEntity for type class java.lang.Object - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1708) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1653) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.2.17.jar:6.2.17] - ... 36 common frames omitted -Caused by: org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.Object - at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:80) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory.getEntityInformation(R2dbcRepositoryFactory.java:123) ~[spring-data-r2dbc-3.5.10.jar:3.5.10] - at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory.getTargetRepository(R2dbcRepositoryFactory.java:110) ~[spring-data-r2dbc-3.5.10.jar:3.5.10] - at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:380) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:355) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.util.Lazy.get(Lazy.java:113) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:361) ~[spring-data-commons-3.5.10.jar:3.5.10] - at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean.afterPropertiesSet(R2dbcRepositoryFactoryBean.java:159) ~[spring-data-r2dbc-3.5.10.jar:3.5.10] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) ~[spring-beans-6.2.17.jar:6.2.17] - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) ~[spring-beans-6.2.17.jar:6.2.17] - ... 45 common frames omitted - - -进程已结束,退出代码为 1 +2026-06-11T13:43:44.371+08:00 TRACE 9324 --- [gym-manage-api] [nio-8084-exec-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [27afd417] HTTP POST "/api/groupCourse/types/1/labels", headers={masked} +2026-06-11T13:43:44.372+08:00 DEBUG 9324 --- [gym-manage-api] [ parallel-2] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession. +2026-06-11T13:43:44.372+08:00 INFO 9324 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : WebFilter 拦截请求: POST /api/groupCourse/types/1/labels +2026-06-11T13:43:44.372+08:00 INFO 9324 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : 未匹配到操作日志配置,跳过: POST /api/groupCourse/types/1/labels +2026-06-11T13:43:44.372+08:00 INFO 9324 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : WebFilter 拦截请求: POST /api/groupCourse/types/1/labels +2026-06-11T13:43:44.372+08:00 INFO 9324 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : 未匹配到操作日志配置,跳过: POST /api/groupCourse/types/1/labels +2026-06-11T13:43:44.372+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.372+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.372+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.372+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.372+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dictionaries" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/change-password" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/logical-delete" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/logical-delete" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/action/restore" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/restore" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/roles" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/menus" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles/{id}/restore" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles/{id}/permissions" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/config" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.373+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/login" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/exception" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/operation" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/login" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/register" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/logout" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dict/types" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dict/data" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/notices" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/messages" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/files/upload" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/permissions" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/auth/miniapp/login" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/auth/mp/callback" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/phone/bind" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/admin/member/{id}/phone" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.375+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-cards" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/purchase" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/renew" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/use" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/refund" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/process-expired" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-transactions" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/page" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/types" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/labels" does not match against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/types/{typeId}/labels" matches against value "/api/groupCourse/types/1/labels" +2026-06-11T13:43:44.376+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.function.server.RouterFunctions : [27afd417] Matched (POST && /api/groupCourse/types/{typeId}/labels) +2026-06-11T13:43:44.376+08:00 DEBUG 9324 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.s.s.RouterFunctionMapping : [27afd417] Mapped to cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000223a09d1920@3760f3e8 +2026-06-11T13:43:44.377+08:00 TRACE 9324 --- [gym-manage-api] [ parallel-2] org.springframework.web.HttpLogging : [27afd417] Decoded [{labelIds=[1, 3, 5]}] +2026-06-11T13:43:44.377+08:00 DEBUG 9324 --- [gym-manage-api] [ parallel-2] o.s.r.c.R2dbcTransactionManager : Creating new transaction with name [cn.novalon.gym.manage.groupcourse.repository.impl.CourseLabelRepository.addLabelsToType]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT +2026-06-11T13:43:44.377+08:00 DEBUG 9324 --- [gym-manage-api] [ parallel-2] o.s.r.c.R2dbcTransactionManager : Acquired Connection [PooledConnection[PostgresqlConnection{client=io.r2dbc.postgresql.client.ReactorNettyClient@c3a5202, codecs=io.r2dbc.postgresql.codec.DefaultCodecs@922d1d4}]] for R2DBC transaction +2026-06-11T13:43:44.377+08:00 DEBUG 9324 --- [gym-manage-api] [ parallel-2] o.s.r.c.R2dbcTransactionManager : Starting R2DBC transaction on Connection [PooledConnection[PostgresqlConnection{client=io.r2dbc.postgresql.client.ReactorNettyClient@c3a5202, codecs=io.r2dbc.postgresql.codec.DefaultCodecs@922d1d4}]] using [ExtendedTransactionDefinition [transactionName='cn.novalon.gym.manage.groupcourse.repository.impl.CourseLabelRepository.addLabelsToType', readOnly=false, isolationLevel=null, lockWaitTimeout=PT0S]] +2026-06-11T13:43:44.379+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT course_type_label.type_id, course_type_label.label_id, course_type_label.id, course_type_label.create_by, course_type_label.update_by, course_type_label.created_at, course_type_label.updated_at, course_type_label.deleted_at FROM course_type_label WHERE course_type_label.type_id = $1 AND (course_type_label.label_id = $2) AND (course_type_label.deleted_at IS NULL)] +2026-06-11T13:43:44.381+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT course_type_label.type_id, course_type_label.label_id, course_type_label.id, course_type_label.create_by, course_type_label.update_by, course_type_label.created_at, course_type_label.updated_at, course_type_label.deleted_at FROM course_type_label WHERE course_type_label.type_id = $1 AND (course_type_label.label_id = $2) AND (course_type_label.deleted_at IS NULL)] +2026-06-11T13:43:44.382+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT course_type_label.type_id, course_type_label.label_id, course_type_label.id, course_type_label.create_by, course_type_label.update_by, course_type_label.created_at, course_type_label.updated_at, course_type_label.deleted_at FROM course_type_label WHERE course_type_label.type_id = $1 AND (course_type_label.label_id = $2) AND (course_type_label.deleted_at IS NULL)] +2026-06-11T13:43:44.382+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO course_type_label (type_id, label_id, create_by, update_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)] +2026-06-11T13:43:44.385+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO course_type_label (type_id, label_id, create_by, update_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)] +2026-06-11T13:43:44.385+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO course_type_label (type_id, label_id, create_by, update_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)] +2026-06-11T13:43:44.387+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r.c.R2dbcTransactionManager : Initiating transaction rollback +2026-06-11T13:43:44.387+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r.c.R2dbcTransactionManager : Rolling back R2DBC transaction on Connection [PooledConnection[PostgresqlConnection{client=io.r2dbc.postgresql.client.ReactorNettyClient@c3a5202, codecs=io.r2dbc.postgresql.codec.DefaultCodecs@922d1d4}]] +2026-06-11T13:43:44.389+08:00 DEBUG 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.r.c.R2dbcTransactionManager : Releasing R2DBC Connection [PooledConnection[PostgresqlConnection{client=io.r2dbc.postgresql.client.ReactorNettyClient@c3a5202, codecs=io.r2dbc.postgresql.codec.DefaultCodecs@922d1d4}]] after transaction +2026-06-11T13:43:44.389+08:00 ERROR 9324 --- [gym-manage-api] [actor-tcp-nio-6] c.n.g.m.g.s.impl.CourseLabelService : 标签添加到类型失败 - typeId=1, error: executeMany; SQL [INSERT INTO course_type_label (type_id, label_id, create_by, update_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)]; 重复键违反唯一约束"course_type_label_type_id_label_id_key" +2026-06-11T13:43:44.389+08:00 TRACE 9324 --- [gym-manage-api] [actor-tcp-nio-6] org.springframework.web.HttpLogging : [27afd417] Encoding [{success=false, message=executeMany; SQL [INSERT INTO course_type_label (type_id, label_id, create_by, update_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)]; 重复键违反唯一约束"course_type_label_type_id_label_id_key"}] +2026-06-11T13:43:44.390+08:00 TRACE 9324 --- [gym-manage-api] [actor-tcp-nio-6] o.s.w.s.adapter.HttpWebHandlerAdapter : [27afd417] Completed 400 BAD_REQUEST, headers={masked} +2026-06-11T13:43:44.390+08:00 TRACE 9324 --- [gym-manage-api] [actor-tcp-nio-6] org.springframework.web.HttpLogging : [27afd417] onComplete diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java index 0ab7700..84512fa 100644 --- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java +++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java @@ -6,6 +6,8 @@ import cn.novalon.gym.manage.datacount.handler.DataStatisticsHandler; import cn.novalon.gym.manage.file.handler.SysFileHandler; import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler; import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler; +import cn.novalon.gym.manage.groupcourse.handler.GroupCourseTypeHandler; +import cn.novalon.gym.manage.groupcourse.handler.CourseLabelHandler; import cn.novalon.gym.manage.member.handler.MemberCardHandler; import cn.novalon.gym.manage.member.handler.MemberCardRecordHandler; import cn.novalon.gym.manage.member.handler.MemberCardTransactionHandler; @@ -69,6 +71,8 @@ public class SystemRouter { MemberCardTransactionHandler memberCardTransactionHandler, GroupCourseHandler groupCourseHandler, GroupCourseBookingHandler groupCourseBookingHandler, + GroupCourseTypeHandler groupCourseTypeHandler, + CourseLabelHandler courseLabelHandler, CheckInHandler checkInHandler, DataStatisticsHandler dataStatisticsHandler) { @@ -265,12 +269,28 @@ public class SystemRouter { // ===== 团课课程管理 ===== .GET("/api/groupCourse/list", groupCourseHandler::getAllGroupCourse) .POST("/api/groupCourse/page", groupCourseHandler::getGroupCoursesByPage) - .GET("/api/groupCourse/{id}", groupCourseHandler::getGroupCourseById) - .POST("/api/groupCourse", groupCourseHandler::createGroupCourse) - .PUT("/api/groupCourse/{id}", groupCourseHandler::updateGroupCourse) - .DELETE("/api/groupCourse/{id}", groupCourseHandler::deleteGroupCourse) - .POST("/api/groupCourse/{id}/cancel", groupCourseHandler::cancelGroupCourse) - .POST("/api/groupCourse/{courseId}/signin", groupCourseHandler::signIn) + + // ===== 团课类型管理 ===== + .GET("/api/groupCourse/types", groupCourseTypeHandler::getAllGroupCourseTypes) + .GET("/api/groupCourse/types/search", groupCourseTypeHandler::searchGroupCourseTypes) + .GET("/api/groupCourse/types/categories", groupCourseTypeHandler::getCategories) + .GET("/api/groupCourse/types/category/{category}", groupCourseTypeHandler::getGroupCourseTypesByCategory) + .GET("/api/groupCourse/types/{id}", groupCourseTypeHandler::getGroupCourseTypeById) + .POST("/api/groupCourse/types", groupCourseTypeHandler::createGroupCourseType) + .PUT("/api/groupCourse/types/{id}", groupCourseTypeHandler::updateGroupCourseType) + .DELETE("/api/groupCourse/types/{id}", groupCourseTypeHandler::deleteGroupCourseType) + + // ===== 团课标签管理 ===== + .GET("/api/groupCourse/labels", courseLabelHandler::getAllLabels) + .GET("/api/groupCourse/labels/search", courseLabelHandler::searchLabels) + .GET("/api/groupCourse/labels/{id}", courseLabelHandler::getLabelById) + .GET("/api/groupCourse/types/{typeId}/labels", courseLabelHandler::getLabelsByTypeId) + .POST("/api/groupCourse/labels", courseLabelHandler::createLabel) + .PUT("/api/groupCourse/labels/{id}", courseLabelHandler::updateLabel) + .DELETE("/api/groupCourse/labels/{id}", courseLabelHandler::deleteLabel) + .POST("/api/groupCourse/types/{typeId}/labels", courseLabelHandler::addLabelsToType) + .DELETE("/api/groupCourse/types/{typeId}/labels/{labelId}", courseLabelHandler::removeLabelFromType) + .DELETE("/api/groupCourse/types/{typeId}/labels", courseLabelHandler::clearLabelsFromType) // ===== 团课预约管理 ===== .POST("/api/groupCourse/book", groupCourseBookingHandler::bookCourse) @@ -278,6 +298,15 @@ public class SystemRouter { .GET("/api/groupCourse/bookings/member/{memberId}", groupCourseBookingHandler::getBookingsByMemberId) .GET("/api/groupCourse/bookings/course/{courseId}", groupCourseBookingHandler::getBookingsByCourseId) .GET("/api/groupCourse/bookings/{bookingId}", groupCourseBookingHandler::getBookingById) + + // ===== 团课课程管理(需要放在具体路由之后)===== + .GET("/api/groupCourse/{id}", groupCourseHandler::getGroupCourseById) + .GET("/api/groupCourse/{id}/detail", groupCourseHandler::getGroupCourseDetailById) + .POST("/api/groupCourse", groupCourseHandler::createGroupCourse) + .PUT("/api/groupCourse/{id}", groupCourseHandler::updateGroupCourse) + .DELETE("/api/groupCourse/{id}", groupCourseHandler::deleteGroupCourse) + .POST("/api/groupCourse/{id}/cancel", groupCourseHandler::cancelGroupCourse) + .POST("/api/groupCourse/{courseId}/signin", groupCourseHandler::signIn) // ========= 签到模块路由 ========== // ===== 签到核心功能 ===== diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql new file mode 100644 index 0000000..fd36b6a --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql @@ -0,0 +1,76 @@ +-- ============================================ +-- 团课类型表 +-- ============================================ + +-- 团课类型表 +CREATE TABLE IF NOT EXISTS group_course_type ( + id BIGSERIAL PRIMARY KEY, + type_name VARCHAR(100) NOT NULL, + base_difficulty INTEGER DEFAULT 1, + description TEXT, + category VARCHAR(50), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +COMMENT ON TABLE group_course_type IS '团课类型表'; +COMMENT ON COLUMN group_course_type.id IS '主键ID'; +COMMENT ON COLUMN group_course_type.type_name IS '类型名称'; +COMMENT ON COLUMN group_course_type.base_difficulty IS '基础难度(1-10)'; +COMMENT ON COLUMN group_course_type.description IS '类型描述'; +COMMENT ON COLUMN group_course_type.category IS '分类(如:有氧、力量、柔韧等)'; +COMMENT ON COLUMN group_course_type.create_by IS '创建人'; +COMMENT ON COLUMN group_course_type.update_by IS '更新人'; +COMMENT ON COLUMN group_course_type.created_at IS '创建时间'; +COMMENT ON COLUMN group_course_type.updated_at IS '更新时间'; +COMMENT ON COLUMN group_course_type.deleted_at IS '删除时间(软删除)'; + +-- 创建索引 +CREATE INDEX idx_group_course_type_type_name ON group_course_type(type_name); +CREATE INDEX idx_group_course_type_category ON group_course_type(category); + +-- 插入初始团课类型数据(参考exmp.txt) +INSERT INTO group_course_type (type_name, base_difficulty, description, category) VALUES +('慢走/椭圆机轻松模式', 1, '几乎无难度,适合所有人', '基础有氧与热身'), +('固定自行车(低阻力)', 2, '注意座椅高度调节即可', '基础有氧与热身'), +('跑步机慢跑', 3, '需要基本协调性,膝盖有压力', '基础有氧与热身'), +('跳绳(连续基础跳)', 3, '需要手脚配合,心肺要求明显', '基础有氧与热身'), +('坐姿腿屈伸/腿弯举', 2, '很容易找到发力感', '固定器械训练'), +('坐姿推胸机', 3, '需注意肩胛后收,避免耸肩', '固定器械训练'), +('高位下拉(坐姿)', 3, '需控制不要过度后仰', '固定器械训练'), +('史密斯机深蹲', 4, '轨迹固定,但需保持核心稳定', '固定器械训练'), +('蝴蝶机夹胸', 3, '易用肘关节代偿,需锁定肩关节', '固定器械训练'), +('平板支撑', 3, '耐力考验,技巧低', '自重基础动作'), +('跪姿俯卧撑', 3, '上肢力量较弱者首选', '自重基础动作'), +('标准俯卧撑', 5, '需核心收紧,身体成直线', '自重基础动作'), +('引体向上(弹力带辅助)', 6, '背部和手臂力量要求高', '自重基础动作'), +('标准引体向上', 8, '力量-体重比极高,多数男性无法完成1次', '自重基础动作'), +('徒手深蹲', 3, '注意膝盖方向与背部直立', '自重基础动作'), +('单腿深蹲(手枪蹲)', 8, '需要极高下肢力量、柔韧性和平衡', '自重基础动作'), +('哑铃二头弯举', 4, '容易晃动借力,但较安全', '自由重量杠铃/哑铃'), +('哑铃侧平举', 5, '极易用斜方肌代偿,真正练到三角肌中束很难', '自由重量杠铃/哑铃'), +('杠铃卧推', 7, '肩关节压力大,起桥、沉肩、稳定手腕均有技巧,有压伤风险', '自由重量杠铃/哑铃'), +('杠铃深蹲(颈后)', 8, '全身协调性、核心抗压、杠位放置、呼吸模式,学习曲线陡峭', '自由重量杠铃/哑铃'), +('传统硬拉', 9, '风险极高,需要精确的脊柱中立、髋铰链、背阔肌收紧,错误时伤腰', '自由重量杠铃/哑铃'), +('高翻/抓举(奥运举重)', 10, '需要爆发力、柔韧、精准衔接,非数月训练不能掌握', '自由重量杠铃/哑铃'), +('波比跳(标准版)', 6, '连续做时心肺压力极大', '高强度与爆发力'), +('冲刺跑(短跑)', 7, '对腘绳肌和脚踝爆发力要求高', '高强度与爆发力'), +('跳箱(合理高度)', 6, '需要落地缓冲技巧', '高强度与爆发力'), +('负重雪橇推', 6, '主要考验腿部耐力和意志力', '高强度与爆发力'), +('双力臂(引体向上后翻腕上杠)', 9, '需要爆发引体 + 极高相对力量', '高强度与爆发力'), +('静态拉伸(坐姿体前屈)', 2, '无风险,但需要坚持', '柔韧与平衡类'), +('瑜伽下犬式', 3, '常见,但需背部与手臂对齐', '柔韧与平衡类'), +('单腿罗马尼亚硬拉(徒手)', 6, '极考验平衡和髋稳定', '柔韧与平衡类'), +('全深蹲(脚跟贴地,亚洲蹲)', 5, '踝关节灵活度限制多数人', '柔韧与平衡类'), +('竖叉/横叉', 8, '需要数月甚至数年拉伸', '柔韧与平衡类'), +('瑜伽入门', 2, '适合初学者的瑜伽课程,注重基础体式', '柔韧与平衡类'), +('瑜伽进阶', 5, '针对有一定基础的学员,包含更复杂体式', '柔韧与平衡类'), +('普拉提', 4, '注重核心力量和身体控制', '柔韧与平衡类'), +('动感单车', 4, '高强度有氧运动,节奏感强', '基础有氧与热身'), +('搏击操', 5, '结合拳击动作的有氧运动', '高强度与爆发力'), +('HIIT训练', 7, '高强度间歇训练,对心肺要求极高', '高强度与爆发力'), +('核心训练', 4, '针对核心肌群的专项训练', '自重基础动作') +ON CONFLICT DO NOTHING; \ No newline at end of file diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql new file mode 100644 index 0000000..d43ca30 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql @@ -0,0 +1,73 @@ +-- ============================================ +-- 团课标签相关表 +-- ============================================ + +-- 团课标签表 +CREATE TABLE IF NOT EXISTS course_label ( + id BIGSERIAL PRIMARY KEY, + label_name VARCHAR(50) NOT NULL, + color VARCHAR(20) DEFAULT '#1890ff', + description VARCHAR(200), + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 团课类型-标签关联表 +CREATE TABLE IF NOT EXISTS course_type_label ( + id BIGSERIAL PRIMARY KEY, + type_id BIGINT NOT NULL, + label_id BIGINT NOT NULL, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + UNIQUE (type_id, label_id) +); + +COMMENT ON TABLE course_label IS '团课标签表'; +COMMENT ON COLUMN course_label.id IS '主键ID'; +COMMENT ON COLUMN course_label.label_name IS '标签名称'; +COMMENT ON COLUMN course_label.color IS '标签颜色(十六进制)'; +COMMENT ON COLUMN course_label.description IS '标签描述'; +COMMENT ON COLUMN course_label.create_by IS '创建人'; +COMMENT ON COLUMN course_label.update_by IS '更新人'; +COMMENT ON COLUMN course_label.created_at IS '创建时间'; +COMMENT ON COLUMN course_label.updated_at IS '更新时间'; +COMMENT ON COLUMN course_label.deleted_at IS '删除时间(软删除)'; + +COMMENT ON TABLE course_type_label IS '团课类型-标签关联表'; +COMMENT ON COLUMN course_type_label.id IS '主键ID'; +COMMENT ON COLUMN course_type_label.type_id IS '团课类型ID'; +COMMENT ON COLUMN course_type_label.label_id IS '标签ID'; +COMMENT ON COLUMN course_type_label.create_by IS '创建人'; +COMMENT ON COLUMN course_type_label.update_by IS '更新人'; +COMMENT ON COLUMN course_type_label.created_at IS '创建时间'; +COMMENT ON COLUMN course_type_label.updated_at IS '更新时间'; +COMMENT ON COLUMN course_type_label.deleted_at IS '删除时间(软删除)'; + +-- 创建索引 +CREATE INDEX idx_course_label_label_name ON course_label(label_name); +CREATE INDEX idx_course_type_label_type_id ON course_type_label(type_id); +CREATE INDEX idx_course_type_label_label_id ON course_type_label(label_id); + +-- 插入初始标签数据 +INSERT INTO course_label (label_name, color, description) VALUES +('适合新手', '#52c41a', '适合健身初学者'), +('中级过渡', '#faad14', '适合有一定基础的学员'), +('高级进阶', '#f5222d', '适合高级学员'), +('减脂塑形', '#722ed1', '有助于减脂塑形'), +('增肌强化', '#13c2c2', '有助于增肌强化'), +('柔韧性训练', '#eb2f96', '注重柔韧性提升'), +('核心训练', '#1890ff', '注重核心力量'), +('心肺训练', '#fa8c16', '提升心肺功能'), +('低冲击', '#52c41a', '低冲击运动,适合关节保护'), +('高强度', '#f5222d', '高强度间歇训练'), +('团体互动', '#722ed1', '注重团队协作'), +('私教推荐', '#13c2c2', '私教推荐课程'), +('热门课程', '#ff1493', '人气较高的课程'), +('新课上线', '#00ced1', '新上线的课程') +ON CONFLICT DO NOTHING; \ No newline at end of file