新增团课推荐功能
This commit was merged in pull request #29.
This commit is contained in:
@@ -0,0 +1,530 @@
|
|||||||
|
# 团课推荐模块 API 文档
|
||||||
|
|
||||||
|
> **文档版本**: v1.0
|
||||||
|
> **创建日期**: 2026-06-15
|
||||||
|
> **作者**: 张翔
|
||||||
|
> **状态**: 正式发布
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
|
||||||
|
1. [概述](#概述)
|
||||||
|
2. [基础路径](#基础路径)
|
||||||
|
3. [团课推荐管理接口](#团课推荐管理接口)
|
||||||
|
- [获取所有团课推荐](#获取所有团课推荐)
|
||||||
|
- [获取所有启用的团课推荐](#获取所有启用的团课推荐)
|
||||||
|
- [根据ID获取团课推荐](#根据ID获取团课推荐)
|
||||||
|
- [根据团课ID获取推荐](#根据团课ID获取推荐)
|
||||||
|
- [创建团课推荐](#创建团课推荐)
|
||||||
|
- [更新团课推荐](#更新团课推荐)
|
||||||
|
- [删除团课推荐](#删除团课推荐)
|
||||||
|
- [启用团课推荐](#启用团课推荐)
|
||||||
|
- [禁用团课推荐](#禁用团课推荐)
|
||||||
|
4. [数据模型](#数据模型)
|
||||||
|
- [GroupCourseRecommend(团课推荐)](#GroupCourseRecommend团课推荐)
|
||||||
|
5. [状态码说明](#状态码说明)
|
||||||
|
6. [业务规则](#业务规则)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
团课推荐模块提供团课推荐信息的创建、编辑、查询、删除和状态管理功能。推荐信息包含团课ID、推荐标题、推荐内容、推荐理由、优先级等必要信息,支持按优先级排序展示。
|
||||||
|
|
||||||
|
## 基础路径
|
||||||
|
|
||||||
|
所有接口的基础路径为: `http://{host}:{port}/api/groupCourse/recommend`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 团课推荐管理接口
|
||||||
|
|
||||||
|
### 获取所有团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | GET |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/list` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| sortBy | string | 否 | priority | 排序字段(支持:priority、createdAt、updatedAt) |
|
||||||
|
| sortOrder | string | 否 | desc | 排序方式(asc-升序,desc-降序) |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"recommendContent": "这是一门非常棒的课程,快来参加吧!",
|
||||||
|
"recommendReason": "教练专业,课程内容丰富",
|
||||||
|
"priority": 10,
|
||||||
|
"isActive": true,
|
||||||
|
"groupCourse": {
|
||||||
|
"id": 1,
|
||||||
|
"courseName": "瑜伽入门",
|
||||||
|
"coachId": 1,
|
||||||
|
"courseType": 1,
|
||||||
|
"startTime": "2026-06-15T09:00:00",
|
||||||
|
"endTime": "2026-06-15T10:00:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 15,
|
||||||
|
"status": 0,
|
||||||
|
"location": "健身房A区",
|
||||||
|
"coverImage": "https://example.com/yoga.jpg",
|
||||||
|
"description": "适合初学者的瑜伽课程"
|
||||||
|
},
|
||||||
|
"createdAt": "2026-06-15T10:00:00",
|
||||||
|
"updatedAt": "2026-06-15T10:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 获取所有启用的团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | GET |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/active` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**功能说明**: 获取系统中所有已启用的团课推荐列表,按优先级从高到低排序。
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"courseId": 3,
|
||||||
|
"recommendTitle": "新学员推荐",
|
||||||
|
"recommendContent": "专为新学员设计的入门课程,轻松上手",
|
||||||
|
"recommendReason": "零基础友好,教练耐心指导",
|
||||||
|
"priority": 20,
|
||||||
|
"isActive": true,
|
||||||
|
"groupCourse": {
|
||||||
|
"id": 3,
|
||||||
|
"courseName": "基础有氧",
|
||||||
|
"coachId": 2,
|
||||||
|
"courseType": 2,
|
||||||
|
"startTime": "2026-06-16T18:00:00",
|
||||||
|
"endTime": "2026-06-16T19:00:00",
|
||||||
|
"maxMembers": 30,
|
||||||
|
"currentMembers": 8,
|
||||||
|
"status": 0,
|
||||||
|
"location": "健身房B区",
|
||||||
|
"coverImage": "https://example.com/aerobic.jpg",
|
||||||
|
"description": "适合所有健身水平的有氧课程"
|
||||||
|
},
|
||||||
|
"createdAt": "2026-06-15T11:00:00",
|
||||||
|
"updatedAt": "2026-06-15T11:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 根据ID获取团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | GET |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/{id}` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 团课推荐ID |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"recommendContent": "这是一门非常棒的课程,快来参加吧!",
|
||||||
|
"recommendReason": "教练专业,课程内容丰富",
|
||||||
|
"priority": 10,
|
||||||
|
"isActive": true,
|
||||||
|
"groupCourse": {
|
||||||
|
"id": 1,
|
||||||
|
"courseName": "瑜伽入门",
|
||||||
|
"coachId": 1,
|
||||||
|
"courseType": 1,
|
||||||
|
"startTime": "2026-06-15T09:00:00",
|
||||||
|
"endTime": "2026-06-15T10:00:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 15,
|
||||||
|
"status": 0,
|
||||||
|
"location": "健身房A区",
|
||||||
|
"coverImage": "https://example.com/yoga.jpg",
|
||||||
|
"description": "适合初学者的瑜伽课程"
|
||||||
|
},
|
||||||
|
"createdAt": "2026-06-15T10:00:00",
|
||||||
|
"updatedAt": "2026-06-15T10:00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (404 Not Found):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 根据团课ID获取推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | GET |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/course/{courseId}` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| courseId | Long | 是 | 团课ID |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"recommendContent": "这是一门非常棒的课程,快来参加吧!",
|
||||||
|
"recommendReason": "教练专业,课程内容丰富",
|
||||||
|
"priority": 10,
|
||||||
|
"isActive": true,
|
||||||
|
"groupCourse": {
|
||||||
|
"id": 1,
|
||||||
|
"courseName": "瑜伽入门",
|
||||||
|
"coachId": 1,
|
||||||
|
"courseType": 1,
|
||||||
|
"startTime": "2026-06-15T09:00:00",
|
||||||
|
"endTime": "2026-06-15T10:00:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 15,
|
||||||
|
"status": 0,
|
||||||
|
"location": "健身房A区"
|
||||||
|
},
|
||||||
|
"createdAt": "2026-06-15T10:00:00",
|
||||||
|
"updatedAt": "2026-06-15T10:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 创建团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | POST |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"recommendContent": "这是一门非常棒的课程,快来参加吧!",
|
||||||
|
"recommendReason": "教练专业,课程内容丰富",
|
||||||
|
"priority": 10,
|
||||||
|
"isActive": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| courseId | Long | **是** | - | 团课ID(必须是有效的团课) |
|
||||||
|
| recommendTitle | String | 否 | - | 推荐标题 |
|
||||||
|
| recommendContent | String | 否 | - | 推荐内容 |
|
||||||
|
| recommendReason | String | 否 | - | 推荐理由 |
|
||||||
|
| priority | Integer | 否 | 0 | 优先级(数字越大优先级越高) |
|
||||||
|
| isActive | Boolean | 否 | true | 是否启用 |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "团课推荐创建成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"recommendContent": "这是一门非常棒的课程,快来参加吧!",
|
||||||
|
"recommendReason": "教练专业,课程内容丰富",
|
||||||
|
"priority": 10,
|
||||||
|
"isActive": true,
|
||||||
|
"createdAt": "2026-06-15T10:00:00",
|
||||||
|
"updatedAt": "2026-06-15T10:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (400 Bad Request):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "团课ID不能为空"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 更新团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | PUT |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/{id}` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 团课推荐ID |
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recommendTitle": "本周热门课程(更新)",
|
||||||
|
"recommendContent": "更新后的推荐内容",
|
||||||
|
"recommendReason": "更新后的推荐理由",
|
||||||
|
"priority": 15,
|
||||||
|
"isActive": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| courseId | Long | 否 | 团课ID |
|
||||||
|
| recommendTitle | String | 否 | 推荐标题 |
|
||||||
|
| recommendContent | String | 否 | 推荐内容 |
|
||||||
|
| recommendReason | String | 否 | 推荐理由 |
|
||||||
|
| priority | Integer | 否 | 优先级 |
|
||||||
|
| isActive | Boolean | 否 | 是否启用 |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "团课推荐更新成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程(更新)",
|
||||||
|
"recommendContent": "更新后的推荐内容",
|
||||||
|
"recommendReason": "更新后的推荐理由",
|
||||||
|
"priority": 15,
|
||||||
|
"isActive": true,
|
||||||
|
"updatedAt": "2026-06-15T12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (400 Bad Request):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "团课推荐不存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 删除团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | DELETE |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/{id}` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 团课推荐ID |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "团课推荐删除成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (400 Bad Request):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "团课推荐不存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 启用团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | POST |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/{id}/enable` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 团课推荐ID |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "团课推荐启用成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"isActive": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (400 Bad Request):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "团课推荐不存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 禁用团课推荐
|
||||||
|
|
||||||
|
| 属性 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| **HTTP方法** | POST |
|
||||||
|
| **接口路径** | `/api/groupCourse/recommend/{id}/disable` |
|
||||||
|
| **所属文件** | `GroupCourseRecommendHandler.java` |
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 团课推荐ID |
|
||||||
|
|
||||||
|
**成功响应** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "团课推荐禁用成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"courseId": 1,
|
||||||
|
"recommendTitle": "本周热门课程",
|
||||||
|
"isActive": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**失败响应** (400 Bad Request):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "团课推荐不存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据模型
|
||||||
|
|
||||||
|
### GroupCourseRecommend(团课推荐)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| id | Long | 主键ID |
|
||||||
|
| courseId | Long | 团课ID(关联group_course.id) |
|
||||||
|
| recommendTitle | String | 推荐标题 |
|
||||||
|
| recommendContent | String | 推荐内容 |
|
||||||
|
| recommendReason | String | 推荐理由 |
|
||||||
|
| priority | Integer | 优先级(数字越大优先级越高),默认0 |
|
||||||
|
| isActive | Boolean | 是否启用,默认true |
|
||||||
|
| groupCourse | GroupCourse | 关联的团课信息(查询时自动填充) |
|
||||||
|
| createdBy | String | 创建人 |
|
||||||
|
| updatedBy | String | 更新人 |
|
||||||
|
| createdAt | LocalDateTime | 创建时间 |
|
||||||
|
| updatedAt | LocalDateTime | 更新时间 |
|
||||||
|
| deletedAt | LocalDateTime | 删除时间(软删除) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 状态码说明
|
||||||
|
|
||||||
|
### 推荐状态
|
||||||
|
|
||||||
|
| 状态值 | 含义 |
|
||||||
|
|--------|------|
|
||||||
|
| true | 启用 |
|
||||||
|
| false | 禁用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 业务规则
|
||||||
|
|
||||||
|
### 团课推荐管理
|
||||||
|
1. **创建推荐**:团课ID为必填项,且必须是有效的团课
|
||||||
|
2. **优先级排序**:获取启用的推荐列表时,按优先级从高到低排序
|
||||||
|
3. **删除推荐**:采用软删除机制,数据保留可恢复
|
||||||
|
4. **状态管理**:支持启用/禁用推荐状态,禁用的推荐不会在推荐列表中显示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录:错误响应格式
|
||||||
|
|
||||||
|
所有接口的错误响应统一格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "错误描述信息"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档结束*
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.dao;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseRecommendEntity;
|
||||||
|
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 GroupCourseRecommendDao extends R2dbcRepository<GroupCourseRecommendEntity, Long> {
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommendEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommendEntity> findAllByDeletedAtIsNull();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommendEntity> findAllByDeletedAtIsNull(Sort sort);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommendEntity> findByCourseIdAndDeletedAtIsNull(Long courseId);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommendEntity> findByCourseIdAndDeletedAtIsNullAndIsActiveTrue(Long courseId);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommendEntity> findByIsActiveTrueAndDeletedAtIsNull();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommendEntity> findByIsActiveTrueAndDeletedAtIsNull(Sort sort);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE group_course_recommend SET is_active = :isActive, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||||
|
Mono<Integer> updateActiveStatus(Long id, Boolean isActive, LocalDateTime updatedAt);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE group_course_recommend SET deleted_at = :deletedAt WHERE id = :id")
|
||||||
|
Mono<Integer> softDelete(Long id, LocalDateTime deletedAt);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE group_course_recommend SET deleted_at = :deletedAt WHERE course_id = :courseId")
|
||||||
|
Mono<Integer> softDeleteByCourseId(Long courseId, LocalDateTime deletedAt);
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
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 GroupCourseRecommend extends BaseDomain {
|
||||||
|
|
||||||
|
@Schema(description = "团课ID", example = "1")
|
||||||
|
private Long courseId;
|
||||||
|
|
||||||
|
@Schema(description = "推荐标题", example = "本周热门课程")
|
||||||
|
private String recommendTitle;
|
||||||
|
|
||||||
|
@Schema(description = "推荐内容", example = "这是一门非常棒的课程,快来参加吧!")
|
||||||
|
private String recommendContent;
|
||||||
|
|
||||||
|
@Schema(description = "推荐理由", example = "教练专业,课程内容丰富")
|
||||||
|
private String recommendReason;
|
||||||
|
|
||||||
|
@Schema(description = "优先级(数字越大优先级越高)", example = "10")
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用", example = "true")
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@Schema(description = "团课信息")
|
||||||
|
private GroupCourse groupCourse;
|
||||||
|
|
||||||
|
public Long getCourseId() {
|
||||||
|
return courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseId(Long courseId) {
|
||||||
|
this.courseId = courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendTitle() {
|
||||||
|
return recommendTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendTitle(String recommendTitle) {
|
||||||
|
this.recommendTitle = recommendTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendContent() {
|
||||||
|
return recommendContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendContent(String recommendContent) {
|
||||||
|
this.recommendContent = recommendContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendReason() {
|
||||||
|
return recommendReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendReason(String recommendReason) {
|
||||||
|
this.recommendReason = recommendReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(Integer priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsActive() {
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsActive(Boolean isActive) {
|
||||||
|
this.isActive = isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupCourse getGroupCourse() {
|
||||||
|
return groupCourse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupCourse(GroupCourse groupCourse) {
|
||||||
|
this.groupCourse = groupCourse;
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Table("group_course_recommend")
|
||||||
|
public class GroupCourseRecommendEntity extends BaseEntity {
|
||||||
|
|
||||||
|
@Column("course_id")
|
||||||
|
private Long courseId;
|
||||||
|
|
||||||
|
@Column("recommend_title")
|
||||||
|
private String recommendTitle;
|
||||||
|
|
||||||
|
@Column("recommend_content")
|
||||||
|
private String recommendContent;
|
||||||
|
|
||||||
|
@Column("recommend_reason")
|
||||||
|
private String recommendReason;
|
||||||
|
|
||||||
|
@Column("priority")
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
@Column("is_active")
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
public Long getCourseId() {
|
||||||
|
return courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseId(Long courseId) {
|
||||||
|
this.courseId = courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendTitle() {
|
||||||
|
return recommendTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendTitle(String recommendTitle) {
|
||||||
|
this.recommendTitle = recommendTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendContent() {
|
||||||
|
return recommendContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendContent(String recommendContent) {
|
||||||
|
this.recommendContent = recommendContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecommendReason() {
|
||||||
|
return recommendReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendReason(String recommendReason) {
|
||||||
|
this.recommendReason = recommendReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(Integer priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsActive() {
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsActive(Boolean isActive) {
|
||||||
|
this.isActive = isActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.handler;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseRecommendService;
|
||||||
|
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 GroupCourseRecommendHandler {
|
||||||
|
|
||||||
|
private final IGroupCourseRecommendService recommendService;
|
||||||
|
|
||||||
|
public GroupCourseRecommendHandler(IGroupCourseRecommendService recommendService) {
|
||||||
|
this.recommendService = recommendService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取所有团课推荐", description = "获取系统中所有团课推荐列表,支持按优先级排序")
|
||||||
|
public Mono<ServerResponse> getAllRecommendations(ServerRequest request) {
|
||||||
|
String sortBy = request.queryParam("sortBy").orElse("priority");
|
||||||
|
String sortOrder = request.queryParam("sortOrder").orElse("desc");
|
||||||
|
|
||||||
|
return ServerResponse.ok()
|
||||||
|
.body(recommendService.findAll(sortBy, sortOrder), GroupCourseRecommend.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取所有启用的团课推荐", description = "获取系统中所有已启用的团课推荐列表(按优先级排序)")
|
||||||
|
public Mono<ServerResponse> getAllActiveRecommendations(ServerRequest request) {
|
||||||
|
return ServerResponse.ok()
|
||||||
|
.body(recommendService.findAllActive(), GroupCourseRecommend.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据ID获取团课推荐", description = "根据ID获取团课推荐详情")
|
||||||
|
public Mono<ServerResponse> getRecommendationById(ServerRequest request) {
|
||||||
|
Long id = Long.valueOf(request.pathVariable("id"));
|
||||||
|
return recommendService.findById(id)
|
||||||
|
.flatMap(recommend -> ServerResponse.ok().bodyValue(recommend))
|
||||||
|
.switchIfEmpty(ServerResponse.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据团课ID获取推荐", description = "根据团课ID获取该团课的推荐信息")
|
||||||
|
public Mono<ServerResponse> getRecommendationsByCourseId(ServerRequest request) {
|
||||||
|
Long courseId = Long.valueOf(request.pathVariable("courseId"));
|
||||||
|
return ServerResponse.ok()
|
||||||
|
.body(recommendService.findByCourseId(courseId), GroupCourseRecommend.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建团课推荐", description = "创建新的团课推荐")
|
||||||
|
public Mono<ServerResponse> createRecommendation(ServerRequest request) {
|
||||||
|
return request.bodyToMono(GroupCourseRecommend.class)
|
||||||
|
.flatMap(recommend -> {
|
||||||
|
if (recommend.getCourseId() == null) {
|
||||||
|
Map<String, Object> error = new HashMap<>();
|
||||||
|
error.put("success", false);
|
||||||
|
error.put("message", "团课ID不能为空");
|
||||||
|
return ServerResponse.badRequest().bodyValue(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendService.create(recommend)
|
||||||
|
.flatMap(r -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "团课推荐创建成功");
|
||||||
|
response.put("data", r);
|
||||||
|
return ServerResponse.ok().bodyValue(response);
|
||||||
|
})
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", error.getMessage());
|
||||||
|
return ServerResponse.badRequest().bodyValue(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新团课推荐", description = "更新指定团课推荐信息")
|
||||||
|
public Mono<ServerResponse> updateRecommendation(ServerRequest request) {
|
||||||
|
Long id = Long.valueOf(request.pathVariable("id"));
|
||||||
|
|
||||||
|
return request.bodyToMono(GroupCourseRecommend.class)
|
||||||
|
.flatMap(recommend -> {
|
||||||
|
return recommendService.update(id, recommend)
|
||||||
|
.flatMap(r -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "团课推荐更新成功");
|
||||||
|
response.put("data", r);
|
||||||
|
return ServerResponse.ok().bodyValue(response);
|
||||||
|
})
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", error.getMessage());
|
||||||
|
return ServerResponse.badRequest().bodyValue(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除团课推荐", description = "删除指定团课推荐(软删除)")
|
||||||
|
public Mono<ServerResponse> deleteRecommendation(ServerRequest request) {
|
||||||
|
Long id = Long.valueOf(request.pathVariable("id"));
|
||||||
|
|
||||||
|
return recommendService.delete(id)
|
||||||
|
.then(Mono.defer(() -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "团课推荐删除成功");
|
||||||
|
return ServerResponse.ok().bodyValue(response);
|
||||||
|
}))
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", error.getMessage());
|
||||||
|
return ServerResponse.badRequest().bodyValue(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "启用团课推荐", description = "启用指定团课推荐")
|
||||||
|
public Mono<ServerResponse> enableRecommendation(ServerRequest request) {
|
||||||
|
Long id = Long.valueOf(request.pathVariable("id"));
|
||||||
|
|
||||||
|
return recommendService.enable(id)
|
||||||
|
.flatMap(r -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "团课推荐启用成功");
|
||||||
|
response.put("data", r);
|
||||||
|
return ServerResponse.ok().bodyValue(response);
|
||||||
|
})
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", error.getMessage());
|
||||||
|
return ServerResponse.badRequest().bodyValue(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "禁用团课推荐", description = "禁用指定团课推荐")
|
||||||
|
public Mono<ServerResponse> disableRecommendation(ServerRequest request) {
|
||||||
|
Long id = Long.valueOf(request.pathVariable("id"));
|
||||||
|
|
||||||
|
return recommendService.disable(id)
|
||||||
|
.flatMap(r -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "团课推荐禁用成功");
|
||||||
|
response.put("data", r);
|
||||||
|
return ServerResponse.ok().bodyValue(response);
|
||||||
|
})
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", error.getMessage());
|
||||||
|
return ServerResponse.badRequest().bodyValue(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.repository;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface IGroupCourseRecommendRepository {
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> findById(Long id);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAll();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAll(String sortBy, String sortOrder);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAllActive();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findByCourseId(Long courseId);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> findActiveByCourseId(Long courseId);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> save(GroupCourseRecommend recommend);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> update(GroupCourseRecommend recommend);
|
||||||
|
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
|
||||||
|
Mono<Void> deleteByCourseId(Long courseId);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> updateActiveStatus(Long id, Boolean isActive);
|
||||||
|
}
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.repository.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseRecommendDao;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseRecommendEntity;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRecommendRepository;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class GroupCourseRecommendRepository implements IGroupCourseRecommendRepository {
|
||||||
|
|
||||||
|
private final GroupCourseRecommendDao recommendDao;
|
||||||
|
private final R2dbcEntityTemplate r2dbcEntityTemplate;
|
||||||
|
|
||||||
|
public GroupCourseRecommendRepository(GroupCourseRecommendDao recommendDao,
|
||||||
|
R2dbcEntityTemplate r2dbcEntityTemplate) {
|
||||||
|
this.recommendDao = recommendDao;
|
||||||
|
this.r2dbcEntityTemplate = r2dbcEntityTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> findById(Long id) {
|
||||||
|
return recommendDao.findByIdAndDeletedAtIsNull(id)
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAll() {
|
||||||
|
return recommendDao.findAllByDeletedAtIsNull()
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAll(String sortBy, String sortOrder) {
|
||||||
|
Sort.Direction direction = "asc".equalsIgnoreCase(sortOrder)
|
||||||
|
? Sort.Direction.ASC
|
||||||
|
: Sort.Direction.DESC;
|
||||||
|
Sort sort = Sort.by(direction, sortBy);
|
||||||
|
return recommendDao.findAllByDeletedAtIsNull(sort)
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAllActive() {
|
||||||
|
return recommendDao.findByIsActiveTrueAndDeletedAtIsNull(Sort.by(Sort.Direction.DESC, "priority"))
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findByCourseId(Long courseId) {
|
||||||
|
return recommendDao.findByCourseIdAndDeletedAtIsNull(courseId)
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> findActiveByCourseId(Long courseId) {
|
||||||
|
return recommendDao.findByCourseIdAndDeletedAtIsNullAndIsActiveTrue(courseId)
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> save(GroupCourseRecommend recommend) {
|
||||||
|
GroupCourseRecommendEntity entity = toEntity(recommend);
|
||||||
|
entity.setCreatedAt(LocalDateTime.now());
|
||||||
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
|
if (entity.getPriority() == null) {
|
||||||
|
entity.setPriority(0);
|
||||||
|
}
|
||||||
|
if (entity.getIsActive() == null) {
|
||||||
|
entity.setIsActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendDao.save(entity)
|
||||||
|
.map(this::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> update(GroupCourseRecommend recommend) {
|
||||||
|
GroupCourseRecommendEntity entity = toEntity(recommend);
|
||||||
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
return r2dbcEntityTemplate.update(entity)
|
||||||
|
.then(findById(recommend.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return recommendDao.softDelete(id, LocalDateTime.now())
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteByCourseId(Long courseId) {
|
||||||
|
return recommendDao.softDeleteByCourseId(courseId, LocalDateTime.now())
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> updateActiveStatus(Long id, Boolean isActive) {
|
||||||
|
return recommendDao.updateActiveStatus(id, isActive, LocalDateTime.now())
|
||||||
|
.flatMap(updated -> {
|
||||||
|
if (updated > 0) {
|
||||||
|
return findById(id);
|
||||||
|
}
|
||||||
|
return Mono.empty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupCourseRecommend toDomain(GroupCourseRecommendEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GroupCourseRecommend recommend = new GroupCourseRecommend();
|
||||||
|
BeanUtil.copyProperties(entity, recommend);
|
||||||
|
return recommend;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupCourseRecommendEntity toEntity(GroupCourseRecommend domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GroupCourseRecommendEntity entity = new GroupCourseRecommendEntity();
|
||||||
|
BeanUtil.copyProperties(domain, entity);
|
||||||
|
if (domain.getId() != null) {
|
||||||
|
entity.markNotNew();
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.service;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface IGroupCourseRecommendService {
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> findById(Long id);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAll();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAll(String sortBy, String sortOrder);
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findAllActive();
|
||||||
|
|
||||||
|
Flux<GroupCourseRecommend> findByCourseId(Long courseId);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> create(GroupCourseRecommend recommend);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> update(Long id, GroupCourseRecommend recommend);
|
||||||
|
|
||||||
|
Mono<Void> delete(Long id);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> enable(Long id);
|
||||||
|
|
||||||
|
Mono<GroupCourseRecommend> disable(Long id);
|
||||||
|
}
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRecommendRepository;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseRecommendService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class GroupCourseRecommendService implements IGroupCourseRecommendService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GroupCourseRecommendService.class);
|
||||||
|
|
||||||
|
private final IGroupCourseRecommendRepository recommendRepository;
|
||||||
|
private final IGroupCourseRepository groupCourseRepository;
|
||||||
|
|
||||||
|
public GroupCourseRecommendService(IGroupCourseRecommendRepository recommendRepository,
|
||||||
|
IGroupCourseRepository groupCourseRepository) {
|
||||||
|
this.recommendRepository = recommendRepository;
|
||||||
|
this.groupCourseRepository = groupCourseRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> findById(Long id) {
|
||||||
|
return recommendRepository.findById(id)
|
||||||
|
.flatMap(this::fillGroupCourseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAll() {
|
||||||
|
return recommendRepository.findAll()
|
||||||
|
.flatMap(this::fillGroupCourseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAll(String sortBy, String sortOrder) {
|
||||||
|
return recommendRepository.findAll(sortBy, sortOrder)
|
||||||
|
.flatMap(this::fillGroupCourseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findAllActive() {
|
||||||
|
return recommendRepository.findAllActive()
|
||||||
|
.flatMap(this::fillGroupCourseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<GroupCourseRecommend> findByCourseId(Long courseId) {
|
||||||
|
return recommendRepository.findByCourseId(courseId)
|
||||||
|
.flatMap(this::fillGroupCourseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> create(GroupCourseRecommend recommend) {
|
||||||
|
if (recommend.getCourseId() == null) {
|
||||||
|
return Mono.error(new RuntimeException("团课ID不能为空"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupCourseRepository.findByIdAndDeletedAtIsNull(recommend.getCourseId())
|
||||||
|
.switchIfEmpty(Mono.error(new RuntimeException("团课不存在")))
|
||||||
|
.flatMap(course -> {
|
||||||
|
return recommendRepository.save(recommend)
|
||||||
|
.doOnSuccess(r -> logger.info("团课推荐创建成功 - id={}, courseId={}", r.getId(), r.getCourseId()))
|
||||||
|
.doOnError(error -> logger.error("团课推荐创建失败 - error: {}", error.getMessage()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> update(Long id, GroupCourseRecommend recommend) {
|
||||||
|
return recommendRepository.findById(id)
|
||||||
|
.switchIfEmpty(Mono.error(new RuntimeException("团课推荐不存在")))
|
||||||
|
.flatMap(existing -> {
|
||||||
|
if (recommend.getRecommendTitle() != null) {
|
||||||
|
existing.setRecommendTitle(recommend.getRecommendTitle());
|
||||||
|
}
|
||||||
|
if (recommend.getRecommendContent() != null) {
|
||||||
|
existing.setRecommendContent(recommend.getRecommendContent());
|
||||||
|
}
|
||||||
|
if (recommend.getRecommendReason() != null) {
|
||||||
|
existing.setRecommendReason(recommend.getRecommendReason());
|
||||||
|
}
|
||||||
|
if (recommend.getPriority() != null) {
|
||||||
|
existing.setPriority(recommend.getPriority());
|
||||||
|
}
|
||||||
|
if (recommend.getIsActive() != null) {
|
||||||
|
existing.setIsActive(recommend.getIsActive());
|
||||||
|
}
|
||||||
|
if (recommend.getCourseId() != null) {
|
||||||
|
existing.setCourseId(recommend.getCourseId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendRepository.update(existing);
|
||||||
|
})
|
||||||
|
.doOnSuccess(r -> logger.info("团课推荐更新成功 - id={}", id))
|
||||||
|
.doOnError(error -> logger.error("团课推荐更新失败 - id={}, error: {}", id, error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> delete(Long id) {
|
||||||
|
return recommendRepository.findById(id)
|
||||||
|
.switchIfEmpty(Mono.error(new RuntimeException("团课推荐不存在")))
|
||||||
|
.flatMap(recommend -> {
|
||||||
|
return recommendRepository.deleteById(id)
|
||||||
|
.doOnSuccess(v -> logger.info("团课推荐删除成功 - id={}", id))
|
||||||
|
.doOnError(error -> logger.error("团课推荐删除失败 - id={}, error: {}", id, error.getMessage()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> enable(Long id) {
|
||||||
|
return recommendRepository.updateActiveStatus(id, true)
|
||||||
|
.switchIfEmpty(Mono.error(new RuntimeException("团课推荐不存在")))
|
||||||
|
.doOnSuccess(r -> logger.info("团课推荐启用成功 - id={}", id))
|
||||||
|
.doOnError(error -> logger.error("团课推荐启用失败 - id={}, error: {}", id, error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<GroupCourseRecommend> disable(Long id) {
|
||||||
|
return recommendRepository.updateActiveStatus(id, false)
|
||||||
|
.switchIfEmpty(Mono.error(new RuntimeException("团课推荐不存在")))
|
||||||
|
.doOnSuccess(r -> logger.info("团课推荐禁用成功 - id={}", id))
|
||||||
|
.doOnError(error -> logger.error("团课推荐禁用失败 - id={}, error: {}", id, error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<GroupCourseRecommend> fillGroupCourseInfo(GroupCourseRecommend recommend) {
|
||||||
|
if (recommend.getCourseId() == null) {
|
||||||
|
return Mono.just(recommend);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupCourseRepository.findByIdAndDeletedAtIsNull(recommend.getCourseId())
|
||||||
|
.map(course -> {
|
||||||
|
recommend.setGroupCourse(course);
|
||||||
|
return recommend;
|
||||||
|
})
|
||||||
|
.defaultIfEmpty(recommend);
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -6,6 +6,7 @@ import cn.novalon.gym.manage.datacount.handler.DataStatisticsHandler;
|
|||||||
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
||||||
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler;
|
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler;
|
||||||
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler;
|
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler;
|
||||||
|
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseRecommendHandler;
|
||||||
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseTypeHandler;
|
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseTypeHandler;
|
||||||
import cn.novalon.gym.manage.groupcourse.handler.CourseLabelHandler;
|
import cn.novalon.gym.manage.groupcourse.handler.CourseLabelHandler;
|
||||||
import cn.novalon.gym.manage.member.handler.MemberCardHandler;
|
import cn.novalon.gym.manage.member.handler.MemberCardHandler;
|
||||||
@@ -71,6 +72,7 @@ public class SystemRouter {
|
|||||||
MemberCardTransactionHandler memberCardTransactionHandler,
|
MemberCardTransactionHandler memberCardTransactionHandler,
|
||||||
GroupCourseHandler groupCourseHandler,
|
GroupCourseHandler groupCourseHandler,
|
||||||
GroupCourseBookingHandler groupCourseBookingHandler,
|
GroupCourseBookingHandler groupCourseBookingHandler,
|
||||||
|
GroupCourseRecommendHandler groupCourseRecommendHandler,
|
||||||
GroupCourseTypeHandler groupCourseTypeHandler,
|
GroupCourseTypeHandler groupCourseTypeHandler,
|
||||||
CourseLabelHandler courseLabelHandler,
|
CourseLabelHandler courseLabelHandler,
|
||||||
CheckInHandler checkInHandler,
|
CheckInHandler checkInHandler,
|
||||||
@@ -299,6 +301,17 @@ public class SystemRouter {
|
|||||||
.GET("/api/groupCourse/bookings/course/{courseId}", groupCourseBookingHandler::getBookingsByCourseId)
|
.GET("/api/groupCourse/bookings/course/{courseId}", groupCourseBookingHandler::getBookingsByCourseId)
|
||||||
.GET("/api/groupCourse/bookings/{bookingId}", groupCourseBookingHandler::getBookingById)
|
.GET("/api/groupCourse/bookings/{bookingId}", groupCourseBookingHandler::getBookingById)
|
||||||
|
|
||||||
|
// ===== 团课推荐管理 =====
|
||||||
|
.GET("/api/groupCourse/recommend/list", groupCourseRecommendHandler::getAllRecommendations)
|
||||||
|
.GET("/api/groupCourse/recommend/active", groupCourseRecommendHandler::getAllActiveRecommendations)
|
||||||
|
.GET("/api/groupCourse/recommend/{id}", groupCourseRecommendHandler::getRecommendationById)
|
||||||
|
.GET("/api/groupCourse/recommend/course/{courseId}", groupCourseRecommendHandler::getRecommendationsByCourseId)
|
||||||
|
.POST("/api/groupCourse/recommend", groupCourseRecommendHandler::createRecommendation)
|
||||||
|
.PUT("/api/groupCourse/recommend/{id}", groupCourseRecommendHandler::updateRecommendation)
|
||||||
|
.DELETE("/api/groupCourse/recommend/{id}", groupCourseRecommendHandler::deleteRecommendation)
|
||||||
|
.POST("/api/groupCourse/recommend/{id}/enable", groupCourseRecommendHandler::enableRecommendation)
|
||||||
|
.POST("/api/groupCourse/recommend/{id}/disable", groupCourseRecommendHandler::disableRecommendation)
|
||||||
|
|
||||||
// ===== 团课课程管理(需要放在具体路由之后)=====
|
// ===== 团课课程管理(需要放在具体路由之后)=====
|
||||||
.GET("/api/groupCourse/{id}", groupCourseHandler::getGroupCourseById)
|
.GET("/api/groupCourse/{id}", groupCourseHandler::getGroupCourseById)
|
||||||
.GET("/api/groupCourse/{id}/detail", groupCourseHandler::getGroupCourseDetailById)
|
.GET("/api/groupCourse/{id}/detail", groupCourseHandler::getGroupCourseDetailById)
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 团课推荐表
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 团课推荐表
|
||||||
|
CREATE TABLE IF NOT EXISTS group_course_recommend (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
course_id BIGINT NOT NULL,
|
||||||
|
recommend_title VARCHAR(200),
|
||||||
|
recommend_content TEXT,
|
||||||
|
recommend_reason VARCHAR(500),
|
||||||
|
priority INTEGER DEFAULT 0,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
create_by VARCHAR(50),
|
||||||
|
update_by VARCHAR(50),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_group_course_recommend_course_id ON group_course_recommend(course_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_group_course_recommend_priority ON group_course_recommend(priority);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_group_course_recommend_is_active ON group_course_recommend(is_active);
|
||||||
|
|
||||||
|
COMMENT ON TABLE group_course_recommend IS '团课推荐表';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.course_id IS '团课ID(关联group_course.id)';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.recommend_title IS '推荐标题';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.recommend_content IS '推荐内容';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.recommend_reason IS '推荐理由';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.priority IS '优先级(数字越大优先级越高)';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.is_active IS '是否启用';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.create_by IS '创建人';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.update_by IS '更新人';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN group_course_recommend.deleted_at IS '删除时间(软删除)';
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 团课推荐测试数据
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 推荐数据1: 极速燃脂单车 - 高优先级推荐(热门课程)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(1, '本周热门推荐', '极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!', '教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著', 20, true, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据2: 清晨流瑜伽 - 中等优先级推荐(适合新手)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(2, '新手友好推荐', '清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', '适合新手入门,教练耐心指导,课程节奏适中', 15, true, 'admin', '2026-06-15 11:00:00', '2026-06-15 11:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据3: 燃脂搏击 - 低优先级推荐(满员课程,已禁用)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(3, '高强度燃脂', '燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。', '高强度训练,适合进阶学员,快速燃脂塑形', 10, false, 'admin', '2026-06-15 12:00:00', '2026-06-15 12:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据4: 哈他瑜伽 - 中等优先级推荐(基础课程)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(4, '基础瑜伽推荐', '基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。', '零基础友好,适合所有健身水平,放松身心', 12, true, 'coach_li', '2026-06-15 13:00:00', '2026-06-15 13:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据5: 蜜桃臀塑造 - 高优先级推荐(热门课程,但课程已结束)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(6, '塑形热门课程', '蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。', '专业私教指导,动作标准,效果显著,深受女性学员喜爱', 18, true, 'coach_li', '2026-05-25 09:15:00', '2026-05-25 09:15:00');
|
||||||
|
|
||||||
|
-- 推荐数据6: 午间冥想放松 - 低优先级推荐(放松课程)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(7, '午间放松推荐', '午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。', '适合上班族,午间放松充电,提升下午工作效率', 8, true, 'admin', '2026-05-25 09:00:00', '2026-05-25 09:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据7: 极速燃脂单车 - 第二个推荐(不同角度推荐)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(1, '减脂首选课程', '想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。', '减脂效果最佳,课程强度适中,适合想要快速瘦身的学员', 16, true, 'coach_zhang', '2026-06-15 14:00:00', '2026-06-15 14:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据8: 清晨流瑜伽 - 第二个推荐(不同角度推荐)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(2, '晨练优选', '清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。', '晨练最佳选择,提升身体活力,改善精神状态', 14, true, 'coach_wang', '2026-06-15 15:00:00', '2026-06-15 15:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据9: 哈他瑜伽 - 第二个推荐(不同角度推荐)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(4, '身心平衡推荐', '哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。', '改善身体柔韧性,增强核心力量,提升身体协调性', 11, true, 'coach_li', '2026-06-15 16:00:00', '2026-06-15 16:00:00');
|
||||||
|
|
||||||
|
-- 推荐数据10: 午间冥想放松 - 第二个推荐(不同角度推荐,已禁用)
|
||||||
|
INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES
|
||||||
|
(7, '职场减压课程', '午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。', '职场减压首选,课程时间短,效果显著', 9, false, 'admin', '2026-05-25 10:00:00', '2026-05-25 10:00:00');
|
||||||
Reference in New Issue
Block a user