# 团课管理模块 API 文档

> **文档版本**: v1.0  
> **创建日期**: 2026-06-02  
> **作者**: 张翔  
> **状态**: 正式发布  

---

## 📋 目录

1. [概述](#概述)
2. [基础路径](#基础路径)
3. [团课管理接口](#团课管理接口)
   - [获取所有团课](#获取所有团课)
   - [分页获取团课](#分页获取团课)
   - [多条件查询团课](#多条件查询团课)
   - [根据ID获取团课详情](#根据ID获取团课详情)
   - [创建团课](#创建团课)
   - [更新团课](#更新团课)
   - [取消团课](#取消团课)
   - [团课签到](#团课签到)
   - [删除团课](#删除团课)
4. [团课预约接口](#团课预约接口)
   - [预约团课](#预约团课)
   - [取消预约](#取消预约)
   - [查询会员预约记录](#查询会员预约记录)
   - [查询预约详情](#查询预约详情)
   - [查询课程预约记录](#查询课程预约记录)
5. [团课类型管理接口](#团课类型管理接口)
   - [获取所有团课类型](#获取所有团课类型)
   - [根据ID获取团课类型](#根据ID获取团课类型)
   - [搜索团课类型](#搜索团课类型)
   - [根据分类获取团课类型](#根据分类获取团课类型)
   - [获取所有分类](#获取所有分类)
   - [创建团课类型](#创建团课类型)
   - [更新团课类型](#更新团课类型)
   - [删除团课类型](#删除团课类型)
6. [团课标签管理接口](#团课标签管理接口)
   - [获取所有标签](#获取所有标签)
   - [根据ID获取标签](#根据ID获取标签)
   - [搜索标签](#搜索标签)
   - [获取类型的标签](#获取类型的标签)
   - [创建标签](#创建标签)
   - [更新标签](#更新标签)
   - [删除标签](#删除标签)
   - [为类型添加标签](#为类型添加标签)
   - [从类型移除标签](#从类型移除标签)
   - [清空类型标签](#清空类型标签)
7. [数据模型](#数据模型)
   - [GroupCourse（团课）](#GroupCourse团课)
   - [GroupCourseBooking（团课预约）](#GroupCourseBooking团课预约)
   - [GroupCourseType（团课类型）](#GroupCourseType团课类型)
7. [状态码说明](#状态码说明)
8. [业务规则](#业务规则)

---

## 概述

团课管理模块提供团课的创建、编辑、查询、取消和签到功能，以及团课预约相关操作。采用 Spring WebFlux 响应式编程，支持高并发场景。

## 基础路径

所有接口的基础路径为: `http://{host}:{port}/api/groupCourse`

---

## 团课管理接口

### 获取所有团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | GET |
| **接口路径** | `/api/groupCourse/list` |
| **所属文件** | `GroupCourseHandler.java` |

**请求参数**:

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| includeDeleted | boolean | 否 | false | 是否包含已删除的团课 |

**成功响应** (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": "适合初学者的瑜伽课程",
    "createdAt": "2026-06-01T10:00:00",
    "updatedAt": "2026-06-01T10:00:00"
  }
]
```

---

### 分页获取团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/page` |
| **所属文件** | `GroupCourseHandler.java` |

**请求体**:

```json
{
  "page": 0,
  "size": 10,
  "sort": "id",
  "order": "asc",
  "keyword": "瑜伽"
}
```

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 0 | 页码，从0开始 |
| size | int | 否 | 10 | 每页数量，最大100 |
| sort | string | 否 | id | 排序字段 |
| order | string | 否 | asc | 排序方式（asc/desc） |
| keyword | string | 否 | - | 搜索关键词 |

**请求参数**:

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| includeDeleted | boolean | 否 | false | 是否包含已删除的团课 |

**成功响应** (200 OK):

```json
{
  "data": [...],
  "totalPages": 5,
  "totalElements": 45,
  "page": 0,
  "size": 10
}
```

---

### 多条件查询团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/search` |
| **所属文件** | `GroupCourseHandler.java` |

**功能说明**: 支持团课名称模糊查询、类型筛选、日期范围、时间段、价格排序、剩余名额排序等多条件组合查询，默认不查询不可预约的团课（已取消或已结束）。

**请求体**:

```json
{
  "courseName": "瑜伽",
  "courseType": 1,
  "startDate": "2026-06-01T00:00:00",
  "endDate": "2026-06-30T23:59:59",
  "timePeriod": "morning",
  "priceSort": "asc",
  "remainingMost": true,
  "page": 0,
  "size": 10
}
```

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| courseName | String | 否 | - | 课程名称（模糊查询，不区分大小写） |
| courseType | Long | 否 | - | 课程类型ID |
| startDate | LocalDateTime | 否 | - | 查询开始日期 |
| endDate | LocalDateTime | 否 | - | 查询结束日期 |
| timePeriod | String | 否 | - | 时间段：`morning`(6:00-12:00)、`afternoon`(12:00-18:00)、`evening`(18:00-24:00) |
| priceSort | String | 否 | - | 价格排序：`asc`(从低到高)、`desc`(从高到低) |
| remainingMost | Boolean | 否 | false | 是否按剩余名额最多排序 |
| page | Integer | 否 | 0 | 页码，从0开始 |
| size | Integer | 否 | 10 | 每页数量，最大100 |

**查询条件优先级说明**:

| 优先级 | 条件类型 | 说明 |
|--------|----------|------|
| 1 | 默认过滤 | 自动过滤已删除和不可预约的团课（status != 0） |
| 2 | 基础筛选 | courseName、courseType、startDate、endDate、timePeriod |
| 3 | 排序规则 | remainingMost优先，其次priceSort，默认按startTime升序 |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "查询成功",
  "data": {
    "content": [
      {
        "id": 1,
        "courseName": "瑜伽入门",
        "coachId": 1,
        "courseType": 1,
        "startTime": "2026-06-15T09:00:00",
        "endTime": "2026-06-15T10:00:00",
        "maxMembers": 20,
        "currentMembers": 5,
        "status": 0,
        "location": "健身房A区",
        "coverImage": "https://example.com/yoga.jpg",
        "description": "适合初学者的瑜伽课程",
        "pointCardAmount": 1,
        "storedValueAmount": 50.00,
        "createdAt": "2026-06-01T10:00:00",
        "updatedAt": "2026-06-01T10:00:00"
      }
    ],
    "totalPages": 3,
    "totalElements": 25,
    "currentPage": 0,
    "pageSize": 10,
    "first": true,
    "last": false
  }
}
```

**失败响应** (400 Bad Request):

```json
{
  "success": false,
  "message": "查询失败的原因"
}
```

**使用示例**:

1. **查询瑜伽课程**
```json
{
  "courseName": "瑜伽"
}
```

2. **查询特定类型的早晨课程**
```json
{
  "courseType": 1,
  "timePeriod": "morning"
}
```

3. **查询下周的课程，按价格从低到高排序**
```json
{
  "startDate": "2026-06-16T00:00:00",
  "endDate": "2026-06-22T23:59:59",
  "priceSort": "asc"
}
```

4. **查询剩余名额最多的晚间课程**
```json
{
  "timePeriod": "evening",
  "remainingMost": true
}
```

5. **多条件组合查询**
```json
{
  "courseName": "瑜伽",
  "courseType": 1,
  "startDate": "2026-06-01T00:00:00",
  "endDate": "2026-06-30T23:59:59",
  "timePeriod": "morning",
  "priceSort": "asc",
  "remainingMost": true,
  "page": 0,
  "size": 10
}
```

---

### 根据ID获取团课详情

| 属性 | 值 |
|------|-----|
| **HTTP方法** | GET |
| **接口路径** | `/api/groupCourse/{id}` |
| **所属文件** | `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,
  "createdAt": "2026-06-01T10:00:00",
  "updatedAt": "2026-06-01T10:00:00"
}
```

**失败响应** (404 Not Found):

```json
{}
```

---

### 根据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
{}
```

**说明**: 此接口返回团课的完整信息，包括：
- 团课基础信息
- 团课对应的类型信息（包含基础难度、综合难度、难度等级等）
- 该类型的所有标签信息

---

### 创建团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse` |
| **所属文件** | `GroupCourseHandler.java` |

**请求体**:

```json
{
  "courseName": "动感单车",
  "coachId": 2,
  "courseType": 2,
  "startTime": "2026-06-05T18:00:00",
  "endTime": "2026-06-05T19:00:00",
  "maxMembers": 25,
  "location": "健身房B区",
  "coverImage": "https://example.com/spinning.jpg",
  "description": "高强度有氧运动课程",
  "pointCardAmount": 1,
  "storedValueAmount": 50.00
}
```

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| courseName | String | **是** | 课程名称 |
| coachId | Long | 否 | 教练ID |
| courseType | Long | 否 | 课程类型 |
| startTime | LocalDateTime | 否 | 开始时间 |
| endTime | LocalDateTime | 否 | 结束时间 |
| maxMembers | Integer | 否 | 最大参与人数，默认20 |
| location | String | 否 | 上课地点 |
| coverImage | String | 否 | 封面图URL |
| description | String | 否 | 课程描述 |
| pointCardAmount | Integer | 否 | 点卡额度（消耗次数），默认1 |
| storedValueAmount | BigDecimal | 否 | 储值卡额度（消耗金额），默认0 |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "团课创建成功",
  "data": {
    "id": 2,
    "courseName": "动感单车",
    "coachId": 2,
    "courseType": 2,
    "startTime": "2026-06-05T18:00:00",
    "endTime": "2026-06-05T19:00:00",
    "maxMembers": 25,
    "currentMembers": 0,
    "status": 0,
    "location": "健身房B区",
    "coverImage": "https://example.com/spinning.jpg",
    "description": "高强度有氧运动课程",
    "pointCardAmount": 1,
    "storedValueAmount": 50.00
  }
}
```

**失败响应** (400 Bad Request):

```json
{
  "success": false,
  "message": "课程名称不能为空"
}
```

---

### 更新团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | PUT |
| **接口路径** | `/api/groupCourse/{id}` |
| **所属文件** | `GroupCourseHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 团课ID |

**请求体**:

```json
{
  "courseName": "动感单车升级版",
  "coachId": 2,
  "maxMembers": 30,
  "description": "升级版高强度有氧运动课程",
  "pointCardAmount": 2,
  "storedValueAmount": 80.00
}
```

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| courseName | String | 否 | 课程名称 |
| coachId | Long | 否 | 教练ID |
| courseType | Long | 否 | 课程类型 |
| startTime | LocalDateTime | 否 | 开始时间 |
| endTime | LocalDateTime | 否 | 结束时间 |
| maxMembers | Integer | 否 | 最大参与人数 |
| location | String | 否 | 上课地点 |
| coverImage | String | 否 | 封面图URL |
| description | String | 否 | 课程描述 |
| pointCardAmount | Integer | 否 | 点卡额度（消耗次数） |
| storedValueAmount | BigDecimal | 否 | 储值卡额度（消耗金额） |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "团课更新成功",
  "data": {
    "id": 2,
    "courseName": "动感单车升级版",
    "coachId": 2,
    "maxMembers": 30,
    "description": "升级版高强度有氧运动课程",
    "pointCardAmount": 2,
    "storedValueAmount": 80.00
  }
}
```

**失败响应** (400 Bad Request):

```json
{
  "success": false,
  "message": "团课不存在"
}
```

---

### 取消团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/{id}/cancel` |
| **所属文件** | `GroupCourseHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 团课ID |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "团课取消成功",
  "data": {
    "id": 2,
    "status": 1
  }
}
```

**失败响应** (400 Bad Request):

```json
{
  "success": false,
  "message": "课程取消需提前24小时"
}
```

---

### 团课签到

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/signin/{memberId}` |
| **所属文件** | `GroupCourseHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| memberId | Long | 是 | 会员ID |

**请求体**:

```json
{
  "courseId": 1
}
```

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| courseId | Long | **是** | 团课ID |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "签到成功",
  "data": {
    "id": 2,
    "currentMembers": 16
  }
}
```

**失败响应** (400 Bad Request) —— 校验规则及对应错误信息:

| 序号 | 校验规则 | 错误信息 |
|------|----------|----------|
| 1 | 团课不存在或已被删除 | `团课不存在或已删除` |
| 2 | 团课状态为"已取消" | `团课已取消，无法签到` |
| 3 | 团课状态为"已结束" | `团课已结束，无法签到` |
| 4 | 当前时间早于开课前2小时 | `未到签到时间，请在开课前2小时内签到` |
| 5 | 当前时间晚于团课结束时间 | `团课已结束，无法签到` |
| 6 | 课程当前人数已达上限 | `课程已满员，无法签到` |
| 7 | 用户今日无到店签到记录 | `请先完成到店签到` |
| 8 | 到店签到状态非SUCCESS | `到店签到未成功，请重新签到` |
| 9 | 用户未预约此课程 | `您未预约此课程，无法签到` |
| - | 请求体为空 | `请求体不能为空` |
| - | 请求体缺少courseId | `courseId不能为空` |

> **说明**: 校验按上表顺序依次执行，命中第一个失败条件即返回对应错误信息。

**失败响应示例**:

```json
{
  "success": false,
  "message": "未到签到时间，请在开课前2小时内签到"
}
```

```json
{
  "success": false,
  "message": "您未预约此课程，无法签到"
}
```

### 删除团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | DELETE |
| **接口路径** | `/api/groupCourse/{id}` |
| **所属文件** | `GroupCourseHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 团课ID |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "团课删除成功"
}
```

---

## 团课预约接口

### 预约团课

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/book` |
| **所属文件** | `GroupCourseBookingHandler.java` |

**请求体**:

```json
{
  "courseId": 1,
  "memberId": 1001,
  "memberCardRecordId": 5001
}
```

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| courseId | Long | **是** | 团课ID |
| memberId | Long | **是** | 会员ID |
| memberCardRecordId | Long | **是** | 会员卡记录ID |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "预约成功",
  "data": {
    "id": 100,
    "courseId": 1,
    "memberId": 1001,
    "memberCardRecordId": 5001,
    "bookingTime": "2026-06-02T08:00:00",
    "status": "0"
  }
}
```

---

### 取消预约

| 属性 | 值 |
|------|-----|
| **HTTP方法** | POST |
| **接口路径** | `/api/groupCourse/booking/{bookingId}/cancel` |
| **所属文件** | `GroupCourseBookingHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| bookingId | Long | 是 | 预约ID |

**请求体**:

```json
{
  "memberId": 1001
}
```

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| memberId | Long | **是** | 会员ID |

**成功响应** (200 OK):

```json
{
  "success": true,
  "message": "取消成功",
  "data": {
    "id": 100,
    "status": "1",
    "cancelTime": "2026-06-02T09:00:00"
  }
}
```

---

### 查询会员预约记录

| 属性 | 值 |
|------|-----|
| **HTTP方法** | GET |
| **接口路径** | `/api/groupCourse/bookings/member/{memberId}` |
| **所属文件** | `GroupCourseBookingHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| memberId | Long | 是 | 会员ID |

**成功响应** (200 OK):

```json
[
  {
    "id": 100,
    "courseId": 1,
    "courseName": "瑜伽入门",
    "memberId": 1001,
    "memberCardRecordId": 5001,
    "bookingTime": "2026-06-02T08:00:00",
    "status": "0",
    "courseStartTime": "2026-06-02T09:00:00",
    "location": "健身房A区"
  }
]
```

---

### 查询预约详情

| 属性 | 值 |
|------|-----|
| **HTTP方法** | GET |
| **接口路径** | `/api/groupCourse/bookings/{bookingId}` |
| **所属文件** | `GroupCourseBookingHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| bookingId | Long | 是 | 预约ID |

**成功响应** (200 OK):

```json
{
  "id": 100,
  "courseId": 1,
  "courseName": "瑜伽入门",
  "memberId": 1001,
  "memberCardRecordId": 5001,
  "bookingTime": "2026-06-02T08:00:00",
  "status": "0",
  "courseStartTime": "2026-06-02T09:00:00",
  "courseEndTime": "2026-06-02T10:00:00",
  "location": "健身房A区"
}
```

**失败响应** (404 Not Found):

```json
{}
```

---

### 查询课程预约记录

| 属性 | 值 |
|------|-----|
| **HTTP方法** | GET |
| **接口路径** | `/api/groupCourse/bookings/course/{courseId}` |
| **所属文件** | `GroupCourseBookingHandler.java` |

**路径参数**:

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| courseId | Long | 是 | 团课ID |

**成功响应** (200 OK):

```json
[
  {
    "id": 100,
    "courseId": 1,
    "courseName": "瑜伽入门",
    "memberId": 1001,
    "bookingTime": "2026-06-02T08:00:00",
    "status": "0"
  }
]
```

---

## 团课类型管理接口

### 获取所有团课类型

| 属性 | 值 |
|------|-----|
| **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\<Long\> | **是** | 要添加的标签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（团课）

| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 主键ID |
| courseName | String | 课程名称 |
| coachId | Long | 教练ID |
| courseType | Long | 课程类型 |
| startTime | LocalDateTime | 开始时间 |
| endTime | LocalDateTime | 结束时间 |
| maxMembers | Integer | 最大参与人数 |
| currentMembers | Integer | 当前参与人数 |
| status | Long | 状态（0-正常，1-已取消，2-已结束） |
| location | String | 上课地点 |
| coverImage | String | 封面图URL |
| description | String | 课程描述 |
| pointCardAmount | Integer | 点卡额度（消耗次数），默认1 |
| storedValueAmount | BigDecimal | 储值卡额度（消耗金额），默认0 |
| createdBy | String | 创建人 |
| updatedBy | String | 更新人 |
| createdAt | LocalDateTime | 创建时间 |
| updatedAt | LocalDateTime | 更新时间 |
| deletedAt | LocalDateTime | 删除时间（软删除） |

### GroupCourseBooking（团课预约）

| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 主键ID |
| courseId | Long | 团课ID |
| courseName | String | 团课名称 |
| memberId | Long | 会员ID |
| memberCardRecordId | Long | 会员卡记录ID |
| bookingTime | LocalDateTime | 预约时间 |
| status | String | 状态（0-已预约，1-已取消，2-已出席，3-缺席） |
| cancelTime | LocalDateTime | 取消时间 |
| courseStartTime | LocalDateTime | 课程开始时间 |
| courseEndTime | LocalDateTime | 课程结束时间 |
| location | String | 上课地点 |
| createdBy | String | 创建人 |
| updatedBy | String | 更新人 |
| createdAt | LocalDateTime | 创建时间 |
| updatedAt | LocalDateTime | 更新时间 |
| deletedAt | LocalDateTime | 删除时间（软删除） |

### GroupCourseType（团课类型）

| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 主键ID |
| typeName | String | 类型名称 |
| baseDifficulty | Integer | 基础难度（1-10） |
| calculatedDifficulty | Integer | 综合难度系数（预留扩展字段） |
| difficultyLevel | String | 难度等级描述（初级/中级/中高级/高级/专家级） |
| description | String | 类型描述 |
| category | String | 分类（如：有氧、力量、柔韧等） |
| labels | List\<CourseLabel\> | 标签列表 |
| 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\<CourseLabel\> | 标签列表 |
| createdAt | LocalDateTime | 创建时间 |
| updatedAt | LocalDateTime | 更新时间 |

**难度等级对应关系**:

| 基础难度 | 难度等级 |
|----------|----------|
| 1-2 | 初级 |
| 3-4 | 中级 |
| 5-6 | 中高级 |
| 7-8 | 高级 |
| 9-10 | 专家级 |

**难度扩展说明**:

`calculatedDifficulty` 字段为预留扩展字段，当前实现仅返回 `baseDifficulty`。未来可扩展的影响因素包括：

1. **课程时长系数**：时长越长难度越高
2. **教练难度调整系数**：教练可根据实际情况微调
3. **会员等级适配系数**：根据会员等级动态调整显示难度
4. **课程强度系数**：高强度课程难度加成

---

## 状态码说明

### 团课状态

| 状态码 | 含义 |
|--------|------|
| 0 | 正常 |
| 1 | 已取消 |
| 2 | 已结束 |

### 预约状态

| 状态码 | 含义 |
|--------|------|
| 0 | 已预约 |
| 1 | 已取消 |
| 2 | 已出席 |
| 3 | 缺席 |

---

## 业务规则

### 团课管理
1. **创建团课**：课程名称为必填项
2. **取消团课**：需提前24小时通知，否则拒绝操作
3. **团课签到**：严格按以下顺序校验，任一不通过即拒绝签到：
   - 团课是否存在且未被删除
   - 团课状态不为"已取消"
   - 团课状态不为"已结束"（含已过结束时间）
   - 当前时间在开课前2小时内（签到时间窗口）
   - 课程当前人数未达到最大人数上限
   - 用户今日已成功到店签到（查询 sign_in_record 表当日 SUCCESS 记录）
   - 用户已预约该课程（有效预约）
4. **删除团课**：采用软删除机制，数据保留可恢复

### 团课预约
1. **预约团课**：需验证会员卡有效性和课程名额
2. **取消预约**：需在课程开始前至少2小时取消

---

## 附录：错误响应格式

所有接口的错误响应统一格式：

```json
{
  "success": false,
  "message": "错误描述信息"
}
```

---

*文档结束*