refactor(service): Service 层重构 - 接口与实现分离
重构以下服务类为接口 + 实现类结构,符合三层架构规范: - CourseCollectionService → CourseCollectionService + CourseCollectionServiceImpl - CourseLessonService → CourseLessonService + CourseLessonServiceImpl - ResourceLibraryService → ResourceLibraryService + ResourceLibraryServiceImpl - ThemeService → ThemeService + ThemeServiceImpl 变更详情: - 接口继承 IService<Entity>,定义业务方法签名 - 实现类继承 ServiceImpl 并实现对应接口,添加 @Override 注解 - CourseCollectionServiceImpl 注入 CourseLessonService 接口依赖 - FileStorageService 保持原结构(纯工具类无需拆分) 验证: - mvn compile -DskipTests 编译通过 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
efedb37cae
commit
67b87fae73
65
docs/dev-logs/2026-03-19.md
Normal file
65
docs/dev-logs/2026-03-19.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# 开发日志 - 2026-03-19
|
||||||
|
|
||||||
|
## Service 层重构
|
||||||
|
|
||||||
|
### 背景
|
||||||
|
|
||||||
|
根据项目统一开发规范要求,Service 层应采用 **接口 + 实现类** 的结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
service/
|
||||||
|
├── XxxService.java ← 接口,继承 IService<Entity>
|
||||||
|
└── impl/
|
||||||
|
└── XxxServiceImpl.java ← 实现类,继承 ServiceImpl 并实现接口
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重构内容
|
||||||
|
|
||||||
|
将以下 5 个服务类从实现类拆分为接口 + 实现类结构:
|
||||||
|
|
||||||
|
| 服务类 | 接口文件 | 实现类文件 | 状态 |
|
||||||
|
|--------|---------|-----------|------|
|
||||||
|
| `ThemeService` | `service/ThemeService.java` | `service/impl/ThemeServiceImpl.java` | ✅ |
|
||||||
|
| `ResourceLibraryService` | `service/ResourceLibraryService.java` | `service/impl/ResourceLibraryServiceImpl.java` | ✅ |
|
||||||
|
| `CourseLessonService` | `service/CourseLessonService.java` | `service/impl/CourseLessonServiceImpl.java` | ✅ |
|
||||||
|
| `CourseCollectionService` | `service/CourseCollectionService.java` | `service/impl/CourseCollectionServiceImpl.java` | ✅ |
|
||||||
|
| `FileStorageService` | 无需拆分(纯工具类) | 保持原结构 | ✅ |
|
||||||
|
|
||||||
|
### 重构详情
|
||||||
|
|
||||||
|
#### 1. ThemeService
|
||||||
|
- **接口方法**:`findAll()`, `findById()`, `create()`, `update()`, `delete()`, `reorder()`
|
||||||
|
- **实现类**:`ThemeServiceImpl` 继承 `ServiceImpl<ThemeMapper, Theme>` 并实现 `ThemeService` 接口
|
||||||
|
|
||||||
|
#### 2. ResourceLibraryService
|
||||||
|
- **接口方法**:`findAllLibraries()`, `findLibraryById()`, `createLibrary()`, `updateLibrary()`, `deleteLibrary()`, `findAllItems()`, `findItemById()`, `createItem()`, `updateItem()`, `deleteItem()`, `batchDeleteItems()`, `getStats()`
|
||||||
|
- **实现类**:`ResourceLibraryServiceImpl` 继承 `ServiceImpl<ResourceLibraryMapper, ResourceLibrary>` 并实现 `ResourceLibraryService` 接口
|
||||||
|
|
||||||
|
#### 3. CourseLessonService
|
||||||
|
- **接口方法**:`findByCourseId()`, `findById()`, `findByType()`, `create()`, `update()`, `delete()`, `reorder()`, `findSteps()`, `createStep()`, `updateStep()`, `deleteStep()`, `reorderSteps()`, `findCourseLessonsForTeacher()`
|
||||||
|
- **实现类**:`CourseLessonServiceImpl` 继承 `ServiceImpl<CourseLessonMapper, CourseLesson>` 并实现 `CourseLessonService` 接口
|
||||||
|
|
||||||
|
#### 4. CourseCollectionService
|
||||||
|
- **接口方法**:`findTenantCollections()`, `getCollectionDetail()`, `pageCollections()`, `getPackagesByCollection()`, `createCollection()`, `setCollectionPackages()`, `updateCollection()`, `deleteCollection()`, `publishCollection()`, `archiveCollection()`, `republishCollection()`, `withdrawCollection()`, `submitCollection()`, `rejectCollection()`, `renewTenantCollection()`
|
||||||
|
- **实现类**:`CourseCollectionServiceImpl` 继承 `ServiceImpl<CourseCollectionMapper, CourseCollection>` 并实现 `CourseCollectionService` 接口
|
||||||
|
- **依赖注入**:注入 `CourseLessonService` 用于查询课程环节
|
||||||
|
|
||||||
|
### 验证结果
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JAVA_HOME="/f/Java/jdk-17"
|
||||||
|
mvn compile -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
**编译结果**:✅ BUILD SUCCESS
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
|
||||||
|
- Controller 层引用保持不变(Spring 自动注入接口实现)
|
||||||
|
- 其他服务层调用保持不变
|
||||||
|
- 无数据库变更
|
||||||
|
- 无 API 变更
|
||||||
|
|
||||||
|
### 备注
|
||||||
|
|
||||||
|
- `FileStorageService` 是纯工具类服务,不涉及数据库操作,无需继承 `IService`,保持当前结构即可
|
||||||
@ -1,625 +1,93 @@
|
|||||||
package com.reading.platform.service;
|
package com.reading.platform.service;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.reading.platform.common.enums.CourseStatus;
|
|
||||||
import com.reading.platform.common.enums.TenantPackageStatus;
|
|
||||||
import com.reading.platform.common.exception.BusinessException;
|
|
||||||
import com.reading.platform.common.response.PageResult;
|
|
||||||
import com.reading.platform.dto.response.CourseCollectionResponse;
|
import com.reading.platform.dto.response.CourseCollectionResponse;
|
||||||
import com.reading.platform.dto.response.CoursePackageResponse;
|
import com.reading.platform.dto.response.CoursePackageResponse;
|
||||||
import com.reading.platform.entity.CoursePackage;
|
|
||||||
import com.reading.platform.entity.CourseCollection;
|
import com.reading.platform.entity.CourseCollection;
|
||||||
import com.reading.platform.entity.CourseCollectionPackage;
|
|
||||||
import com.reading.platform.entity.CoursePackageCourse;
|
|
||||||
import com.reading.platform.entity.CourseLesson;
|
|
||||||
import com.reading.platform.entity.TenantPackage;
|
|
||||||
import com.reading.platform.mapper.CourseCollectionMapper;
|
|
||||||
import com.reading.platform.mapper.CourseCollectionPackageMapper;
|
|
||||||
import com.reading.platform.mapper.CoursePackageMapper;
|
|
||||||
import com.reading.platform.mapper.CoursePackageCourseMapper;
|
|
||||||
import com.reading.platform.mapper.TenantPackageMapper;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 课程套餐服务(两层结构-最上层)
|
* 课程套餐服务接口(两层结构 - 最上层)
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface CourseCollectionService extends IService<CourseCollection> {
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper, CourseCollection> {
|
|
||||||
|
|
||||||
private final CourseCollectionMapper collectionMapper;
|
|
||||||
private final CourseCollectionPackageMapper collectionPackageMapper;
|
|
||||||
private final CoursePackageMapper packageMapper;
|
|
||||||
private final TenantPackageMapper tenantPackageMapper;
|
|
||||||
private final CoursePackageCourseMapper packageCoursePackageMapper;
|
|
||||||
private final CoursePackageMapper courseMapper;
|
|
||||||
private final CourseLessonService courseLessonService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询租户的课程套餐列表
|
* 查询租户的课程套餐列表
|
||||||
*/
|
*/
|
||||||
public List<CourseCollectionResponse> findTenantCollections(Long tenantId) {
|
List<CourseCollectionResponse> findTenantCollections(Long tenantId);
|
||||||
log.info("查询租户课程套餐,tenantId={}", tenantId);
|
|
||||||
|
|
||||||
// 查询租户的课程套餐关联
|
|
||||||
List<TenantPackage> tenantPackages = tenantPackageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getTenantId, tenantId)
|
|
||||||
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE.getCode())
|
|
||||||
.isNotNull(TenantPackage::getCollectionId)
|
|
||||||
.orderByDesc(TenantPackage::getCreatedAt)
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info("查询到{}条租户套餐关联记录", tenantPackages.size());
|
|
||||||
for (TenantPackage tp : tenantPackages) {
|
|
||||||
log.info(" - collection_id: {}, start_date: {}, end_date: {}",
|
|
||||||
tp.getCollectionId(), tp.getStartDate(), tp.getEndDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取套餐详情并转换为响应对象
|
|
||||||
List<CourseCollectionResponse> result = tenantPackages.stream()
|
|
||||||
.map(tp -> {
|
|
||||||
CourseCollection collection = collectionMapper.selectById(tp.getCollectionId());
|
|
||||||
if (collection == null) {
|
|
||||||
log.warn("套餐不存在,collection_id={}", tp.getCollectionId());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 过滤下架状态的课程套餐
|
|
||||||
if (CourseStatus.ARCHIVED.getCode().equals(collection.getStatus())) {
|
|
||||||
log.warn("套餐已下架,collection_id={}", tp.getCollectionId());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
log.info("返回套餐: id={}, name={}, package_count={}",
|
|
||||||
collection.getId(), collection.getName(), collection.getPackageCount());
|
|
||||||
CourseCollectionResponse response = toResponse(collection);
|
|
||||||
// 设置租户套餐的额外信息(如果有)
|
|
||||||
response.setStartDate(tp.getStartDate());
|
|
||||||
response.setEndDate(tp.getEndDate());
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
.filter(r -> r != null)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
log.info("查询到{}个课程套餐", result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取课程套餐详情(包含课程包列表)
|
* 获取课程套餐详情(包含课程包列表)
|
||||||
*/
|
*/
|
||||||
public CourseCollectionResponse getCollectionDetail(Long collectionId) {
|
CourseCollectionResponse getCollectionDetail(Long collectionId);
|
||||||
log.info("获取课程套餐详情,collectionId={}", collectionId);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(collectionId);
|
|
||||||
if (collection == null) {
|
|
||||||
log.warn("课程套餐不存在,collectionId={}", collectionId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toResponse(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询课程套餐
|
* 分页查询课程套餐
|
||||||
*/
|
*/
|
||||||
public Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status) {
|
Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status);
|
||||||
log.info("分页查询课程套餐,pageNum={}, pageSize={}, status={}", pageNum, pageSize, status);
|
|
||||||
|
|
||||||
LambdaQueryWrapper<CourseCollection> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
if (StringUtils.hasText(status)) {
|
|
||||||
wrapper.eq(CourseCollection::getStatus, status);
|
|
||||||
}
|
|
||||||
wrapper.orderByDesc(CourseCollection::getCreatedAt);
|
|
||||||
|
|
||||||
Page<CourseCollection> page = collectionMapper.selectPage(
|
|
||||||
new Page<>(pageNum, pageSize),
|
|
||||||
wrapper
|
|
||||||
);
|
|
||||||
|
|
||||||
// 转换为响应对象
|
|
||||||
List<CourseCollectionResponse> responses = page.getRecords().stream()
|
|
||||||
.map(this::toResponse)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
Page<CourseCollectionResponse> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
|
||||||
result.setRecords(responses);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取课程套餐下的课程包列表
|
* 获取课程套餐下的课程包列表
|
||||||
*/
|
*/
|
||||||
public List<CoursePackageResponse> getPackagesByCollection(Long collectionId) {
|
List<CoursePackageResponse> getPackagesByCollection(Long collectionId);
|
||||||
log.info("获取课程套餐的课程包列表,collectionId={}", collectionId);
|
|
||||||
|
|
||||||
// 查询关联关系
|
|
||||||
List<CourseCollectionPackage> associations = collectionPackageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CourseCollectionPackage>()
|
|
||||||
.eq(CourseCollectionPackage::getCollectionId, collectionId)
|
|
||||||
.orderByAsc(CourseCollectionPackage::getSortOrder)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (associations.isEmpty()) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取课程包详情
|
|
||||||
List<Long> packageIds = associations.stream()
|
|
||||||
.map(CourseCollectionPackage::getPackageId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
List<CoursePackage> packages = packageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CoursePackage>()
|
|
||||||
.in(CoursePackage::getId, packageIds)
|
|
||||||
.eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 转换为响应对象并设置排序号
|
|
||||||
List<CoursePackageResponse> result = packages.stream()
|
|
||||||
.map(pkg -> {
|
|
||||||
CoursePackageResponse response = toPackageResponse(pkg);
|
|
||||||
// 设置排序号
|
|
||||||
associations.stream()
|
|
||||||
.filter(a -> a.getPackageId().equals(pkg.getId()))
|
|
||||||
.findFirst()
|
|
||||||
.ifPresent(a -> response.setSortOrder(a.getSortOrder()));
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
log.info("查询到{}个课程包", result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建课程套餐
|
* 创建课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
CourseCollectionResponse createCollection(String name, String description, Long price,
|
||||||
public CourseCollectionResponse createCollection(String name, String description, Long price,
|
Long discountPrice, String discountType, String[] gradeLevels);
|
||||||
Long discountPrice, String discountType, String[] gradeLevels) {
|
|
||||||
log.info("创建课程套餐,name={}", name);
|
|
||||||
|
|
||||||
CourseCollection collection = new CourseCollection();
|
|
||||||
collection.setName(name);
|
|
||||||
collection.setDescription(description);
|
|
||||||
collection.setPrice(price);
|
|
||||||
collection.setDiscountPrice(discountPrice);
|
|
||||||
collection.setDiscountType(discountType);
|
|
||||||
// 将数组转为 JSON 字符串存储到 JSON 字段
|
|
||||||
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
|
|
||||||
collection.setPackageCount(0);
|
|
||||||
collection.setStatus(CourseStatus.DRAFT.getCode());
|
|
||||||
|
|
||||||
collectionMapper.insert(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐创建成功,id={}", collection.getId());
|
|
||||||
return toResponse(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置课程套餐的课程包
|
* 设置课程套餐的课程包
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void setCollectionPackages(Long collectionId, List<Long> packageIds);
|
||||||
public void setCollectionPackages(Long collectionId, List<Long> packageIds) {
|
|
||||||
log.info("设置课程套餐的课程包,collectionId={}, packageCount={}", collectionId, packageIds.size());
|
|
||||||
|
|
||||||
// 验证课程套餐是否存在
|
|
||||||
CourseCollection collection = collectionMapper.selectById(collectionId);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new BusinessException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去重:保持首次出现顺序,避免 uk_collection_package 唯一约束冲突
|
|
||||||
List<Long> distinctIds = packageIds.stream().distinct().collect(Collectors.toList());
|
|
||||||
|
|
||||||
// 验证课程包是否存在(应用层外键约束)
|
|
||||||
if (!distinctIds.isEmpty()) {
|
|
||||||
List<CoursePackage> packages = packageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CoursePackage>()
|
|
||||||
.in(CoursePackage::getId, distinctIds)
|
|
||||||
);
|
|
||||||
if (packages.size() != distinctIds.size()) {
|
|
||||||
throw new BusinessException("存在无效的课程包 ID");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除旧的关联
|
|
||||||
collectionPackageMapper.delete(
|
|
||||||
new LambdaQueryWrapper<CourseCollectionPackage>()
|
|
||||||
.eq(CourseCollectionPackage::getCollectionId, collectionId)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 创建新的关联
|
|
||||||
for (int i = 0; i < distinctIds.size(); i++) {
|
|
||||||
CourseCollectionPackage association = new CourseCollectionPackage();
|
|
||||||
association.setCollectionId(collectionId);
|
|
||||||
association.setPackageId(distinctIds.get(i));
|
|
||||||
association.setSortOrder(i + 1);
|
|
||||||
collectionPackageMapper.insert(association);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新课程包数量(复用前面已验证的 collection 变量)
|
|
||||||
collection.setPackageCount(distinctIds.size());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐的课程包设置完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新课程套餐
|
* 更新课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
CourseCollectionResponse updateCollection(Long id, String name, String description, Long price,
|
||||||
public CourseCollectionResponse updateCollection(Long id, String name, String description, Long price,
|
Long discountPrice, String discountType, String[] gradeLevels);
|
||||||
Long discountPrice, String discountType, String[] gradeLevels) {
|
|
||||||
log.info("更新课程套餐,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setName(name);
|
|
||||||
collection.setDescription(description);
|
|
||||||
collection.setPrice(price);
|
|
||||||
collection.setDiscountPrice(discountPrice);
|
|
||||||
collection.setDiscountType(discountType);
|
|
||||||
// 将数组转为 JSON 字符串存储到 JSON 字段
|
|
||||||
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
|
|
||||||
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐更新成功,id={}", id);
|
|
||||||
return toResponse(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除课程套餐
|
* 删除课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void deleteCollection(Long id);
|
||||||
public void deleteCollection(Long id) {
|
|
||||||
log.info("删除课程套餐,id={}", id);
|
|
||||||
|
|
||||||
// 检查是否有租户正在使用此课程套餐
|
|
||||||
Long tenantCount = tenantPackageMapper.selectCount(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getCollectionId, id)
|
|
||||||
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tenantCount > 0) {
|
|
||||||
log.warn("删除课程套餐失败,有 {} 个租户正在使用此套餐,id={}", tenantCount, id);
|
|
||||||
throw new BusinessException("有 " + tenantCount + " 个租户正在使用此课程套餐,无法删除");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理所有状态的租户套餐关联记录(包括非活跃状态)
|
|
||||||
List<TenantPackage> allTenantPackages = tenantPackageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getCollectionId, id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!allTenantPackages.isEmpty()) {
|
|
||||||
tenantPackageMapper.delete(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getCollectionId, id)
|
|
||||||
);
|
|
||||||
log.info("已清理 {} 条租户套餐关联记录", allTenantPackages.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除课程套餐与课程包的关联关系
|
|
||||||
List<CourseCollectionPackage> collectionPackages = collectionPackageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CourseCollectionPackage>()
|
|
||||||
.eq(CourseCollectionPackage::getCollectionId, id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!collectionPackages.isEmpty()) {
|
|
||||||
collectionPackageMapper.delete(
|
|
||||||
new LambdaQueryWrapper<CourseCollectionPackage>()
|
|
||||||
.eq(CourseCollectionPackage::getCollectionId, id)
|
|
||||||
);
|
|
||||||
log.info("已清理 {} 条课程包关联记录", collectionPackages.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除课程套餐
|
|
||||||
collectionMapper.deleteById(id);
|
|
||||||
|
|
||||||
log.info("课程套餐删除成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布课程套餐
|
* 发布课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void publishCollection(Long id);
|
||||||
public void publishCollection(Long id) {
|
|
||||||
log.info("发布课程套餐,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.PUBLISHED.getCode());
|
|
||||||
collection.setPublishedAt(LocalDateTime.now());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐发布成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下架课程套餐
|
* 下架课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void archiveCollection(Long id);
|
||||||
public void archiveCollection(Long id) {
|
|
||||||
log.info("下架课程套餐,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.ARCHIVED.getCode());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐下架成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新发布课程套餐
|
* 重新发布课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void republishCollection(Long id);
|
||||||
public void republishCollection(Long id) {
|
|
||||||
log.info("重新发布课程套餐,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.PUBLISHED.getCode());
|
|
||||||
collection.setPublishedAt(LocalDateTime.now());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐重新发布成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 撤销审核
|
* 撤销审核
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void withdrawCollection(Long id);
|
||||||
public void withdrawCollection(Long id) {
|
|
||||||
log.info("撤销课程套餐审核,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.DRAFT.getCode());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐审核撤销成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交课程套餐审核
|
* 提交课程套餐审核
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void submitCollection(Long id);
|
||||||
public void submitCollection(Long id) {
|
|
||||||
log.info("提交课程套餐审核,id={}", id);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否包含课程包
|
|
||||||
if (collection.getPackageCount() == null || collection.getPackageCount() == 0) {
|
|
||||||
throw new BusinessException("课程套餐必须包含至少一个课程包");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.PENDING.getCode());
|
|
||||||
collection.setSubmittedAt(LocalDateTime.now());
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐提交审核成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 审核驳回课程套餐
|
* 审核驳回课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void rejectCollection(Long id, String comment);
|
||||||
public void rejectCollection(Long id, String comment) {
|
|
||||||
log.info("审核驳回课程套餐,id={}, comment={}", id, comment);
|
|
||||||
|
|
||||||
CourseCollection collection = collectionMapper.selectById(id);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.setStatus(CourseStatus.REJECTED.getCode());
|
|
||||||
collection.setReviewedAt(LocalDateTime.now());
|
|
||||||
collection.setReviewComment(comment);
|
|
||||||
collectionMapper.updateById(collection);
|
|
||||||
|
|
||||||
log.info("课程套餐审核驳回成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 续费租户课程套餐
|
* 续费租户课程套餐
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void renewTenantCollection(Long tenantId, Long collectionId, LocalDate endDate, Long pricePaid);
|
||||||
public void renewTenantCollection(Long tenantId, Long collectionId, java.time.LocalDate endDate, Long pricePaid) {
|
|
||||||
log.info("续费租户课程套餐,tenantId={}, collectionId={}, endDate={}, pricePaid={}", tenantId, collectionId, endDate, pricePaid);
|
|
||||||
|
|
||||||
// 查询现有租户套餐关联
|
|
||||||
List<TenantPackage> existingPackages = tenantPackageMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getTenantId, tenantId)
|
|
||||||
.eq(TenantPackage::getCollectionId, collectionId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!existingPackages.isEmpty()) {
|
|
||||||
// 更新现有记录
|
|
||||||
TenantPackage existing = existingPackages.get(0);
|
|
||||||
existing.setEndDate(endDate);
|
|
||||||
existing.setStatus(TenantPackageStatus.ACTIVE);
|
|
||||||
if (pricePaid != null) {
|
|
||||||
existing.setPricePaid(pricePaid);
|
|
||||||
}
|
|
||||||
existing.setUpdatedAt(LocalDateTime.now());
|
|
||||||
tenantPackageMapper.updateById(existing);
|
|
||||||
log.info("租户课程套餐续费成功,tenantId={}, collectionId={}", tenantId, collectionId);
|
|
||||||
} else {
|
|
||||||
// 查询课程套餐信息
|
|
||||||
CourseCollection collection = collectionMapper.selectById(collectionId);
|
|
||||||
if (collection == null) {
|
|
||||||
throw new IllegalArgumentException("课程套餐不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新记录
|
|
||||||
TenantPackage tp = new TenantPackage();
|
|
||||||
tp.setTenantId(tenantId);
|
|
||||||
tp.setCollectionId(collectionId);
|
|
||||||
tp.setStartDate(java.time.LocalDate.now());
|
|
||||||
tp.setEndDate(endDate);
|
|
||||||
tp.setStatus(TenantPackageStatus.ACTIVE);
|
|
||||||
tp.setPricePaid(pricePaid != null ? pricePaid :
|
|
||||||
(collection.getDiscountPrice() != null ? collection.getDiscountPrice() : collection.getPrice()));
|
|
||||||
tp.setCreatedAt(LocalDateTime.now());
|
|
||||||
tenantPackageMapper.insert(tp);
|
|
||||||
log.info("租户课程套餐新办成功,tenantId={}, collectionId={}", tenantId, collectionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析适用年级:数据库存储为 JSON 数组 ["小班","中班","大班"],需正确解析
|
|
||||||
*/
|
|
||||||
private String[] parseGradeLevels(String gradeLevels) {
|
|
||||||
if (!StringUtils.hasText(gradeLevels)) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (gradeLevels.trim().startsWith("[")) {
|
|
||||||
return JSON.parseArray(gradeLevels, String.class).toArray(new String[0]);
|
|
||||||
}
|
|
||||||
// 兼容旧数据:逗号分隔格式
|
|
||||||
return gradeLevels.split(",");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("解析 gradeLevels 失败: {}", gradeLevels, e);
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为响应对象
|
|
||||||
*/
|
|
||||||
private CourseCollectionResponse toResponse(CourseCollection collection) {
|
|
||||||
// 获取课程包列表
|
|
||||||
List<CourseCollectionResponse.CoursePackageItem> packages = getPackagesByCollection(collection.getId()).stream()
|
|
||||||
.map(pkg -> {
|
|
||||||
CourseCollectionResponse.CoursePackageItem item = new CourseCollectionResponse.CoursePackageItem();
|
|
||||||
item.setId(pkg.getId());
|
|
||||||
item.setName(pkg.getName());
|
|
||||||
item.setDescription(pkg.getDescription());
|
|
||||||
item.setGradeLevels(pkg.getGradeLevels() != null ? pkg.getGradeLevels() : new String[0]);
|
|
||||||
item.setCourseCount(pkg.getCourseCount() != null ? pkg.getCourseCount() : 0);
|
|
||||||
item.setSortOrder(pkg.getSortOrder());
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return CourseCollectionResponse.builder()
|
|
||||||
.id(collection.getId())
|
|
||||||
.name(collection.getName())
|
|
||||||
.description(collection.getDescription())
|
|
||||||
.price(collection.getPrice())
|
|
||||||
.discountPrice(collection.getDiscountPrice())
|
|
||||||
.discountType(collection.getDiscountType())
|
|
||||||
.gradeLevels(parseGradeLevels(collection.getGradeLevels()))
|
|
||||||
.packageCount(collection.getPackageCount())
|
|
||||||
.status(collection.getStatus())
|
|
||||||
.submittedAt(collection.getSubmittedAt())
|
|
||||||
.submittedBy(collection.getSubmittedBy())
|
|
||||||
.reviewedAt(collection.getReviewedAt())
|
|
||||||
.reviewedBy(collection.getReviewedBy())
|
|
||||||
.reviewComment(collection.getReviewComment())
|
|
||||||
.publishedAt(collection.getPublishedAt())
|
|
||||||
.createdAt(collection.getCreatedAt())
|
|
||||||
.updatedAt(collection.getUpdatedAt())
|
|
||||||
.packages(packages)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为课程包响应对象(包含课程列表和排课计划参考)
|
|
||||||
*/
|
|
||||||
private CoursePackageResponse toPackageResponse(CoursePackage pkg) {
|
|
||||||
// 解析 gradeTags(CoursePackage 使用 gradeTags)
|
|
||||||
String[] gradeLevelsArray = new String[0];
|
|
||||||
if (pkg.getGradeTags() != null && !pkg.getGradeTags().isEmpty()) {
|
|
||||||
try {
|
|
||||||
// gradeTags 是 JSON 数组格式,尝试解析
|
|
||||||
if (pkg.getGradeTags().startsWith("[")) {
|
|
||||||
gradeLevelsArray = com.alibaba.fastjson2.JSON.parseArray(pkg.getGradeTags(), String.class).toArray(new String[0]);
|
|
||||||
} else {
|
|
||||||
gradeLevelsArray = pkg.getGradeTags().split(",");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
gradeLevelsArray = new String[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CoursePackageResponse.CoursePackageCourseItem> courseItems = new ArrayList<>();
|
|
||||||
|
|
||||||
// 查询课程包关联的课程环节
|
|
||||||
List<CourseLesson> lessons = courseLessonService.findByCourseId(pkg.getId());
|
|
||||||
|
|
||||||
if (!lessons.isEmpty()) {
|
|
||||||
courseItems = lessons.stream()
|
|
||||||
.map(lesson -> {
|
|
||||||
CoursePackageResponse.CoursePackageCourseItem item = new CoursePackageResponse.CoursePackageCourseItem();
|
|
||||||
item.setId(lesson.getId());
|
|
||||||
item.setName(lesson.getName());
|
|
||||||
item.setGradeLevel(null); // 课程环节没有年级字段
|
|
||||||
item.setSortOrder(lesson.getSortOrder());
|
|
||||||
item.setScheduleRefData(pkg.getScheduleRefData());
|
|
||||||
item.setLessonType(lesson.getLessonType());
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoursePackageResponse.builder()
|
|
||||||
.id(pkg.getId())
|
|
||||||
.name(pkg.getName())
|
|
||||||
.description(pkg.getDescription())
|
|
||||||
.price(null) // CoursePackage 没有价格字段
|
|
||||||
.discountPrice(null)
|
|
||||||
.discountType(null)
|
|
||||||
.gradeLevels(gradeLevelsArray)
|
|
||||||
.courseCount(lessons.size()) // 使用课程环节数量
|
|
||||||
.status(pkg.getStatus())
|
|
||||||
.submittedAt(pkg.getSubmittedAt())
|
|
||||||
.submittedBy(pkg.getSubmittedBy())
|
|
||||||
.reviewedAt(pkg.getReviewedAt())
|
|
||||||
.reviewedBy(pkg.getReviewedBy())
|
|
||||||
.reviewComment(pkg.getReviewComment())
|
|
||||||
.publishedAt(pkg.getPublishedAt())
|
|
||||||
.createdAt(pkg.getCreatedAt())
|
|
||||||
.updatedAt(pkg.getUpdatedAt())
|
|
||||||
.courses(courseItems)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,417 +1,87 @@
|
|||||||
package com.reading.platform.service;
|
package com.reading.platform.service;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.reading.platform.entity.CourseLesson;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.reading.platform.entity.LessonStep;
|
||||||
import com.reading.platform.common.enums.TenantPackageStatus;
|
|
||||||
import com.reading.platform.common.exception.BusinessException;
|
|
||||||
import com.reading.platform.entity.*;
|
|
||||||
import com.reading.platform.mapper.*;
|
|
||||||
import com.reading.platform.mapper.TenantCourseMapper;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 课程环节服务
|
* 课程环节服务接口
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface CourseLessonService extends IService<CourseLesson> {
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CourseLessonService extends ServiceImpl<CourseLessonMapper, CourseLesson> {
|
|
||||||
|
|
||||||
private final CourseLessonMapper courseLessonMapper;
|
|
||||||
private final LessonStepMapper lessonStepMapper;
|
|
||||||
private final LessonStepResourceMapper lessonStepResourceMapper;
|
|
||||||
private final TenantCourseMapper tenantCourseMapper;
|
|
||||||
private final TenantPackageMapper tenantPackageMapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询课程的所有环节
|
* 查询课程的所有环节
|
||||||
*/
|
*/
|
||||||
public List<CourseLesson> findByCourseId(Long courseId) {
|
List<CourseLesson> findByCourseId(Long courseId);
|
||||||
log.info("查询课程的所有环节,courseId={}", courseId);
|
|
||||||
List<CourseLesson> result = courseLessonMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CourseLesson>()
|
|
||||||
.eq(CourseLesson::getCourseId, courseId)
|
|
||||||
.orderByAsc(CourseLesson::getSortOrder)
|
|
||||||
);
|
|
||||||
log.info("查询课程环节成功,courseId={}, count={}", courseId, result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询课程环节详情
|
* 查询课程环节详情
|
||||||
*/
|
*/
|
||||||
public CourseLesson findById(Long id) {
|
CourseLesson findById(Long id);
|
||||||
log.info("查询课程环节详情,id={}", id);
|
|
||||||
CourseLesson lesson = courseLessonMapper.selectById(id);
|
|
||||||
if (lesson == null) {
|
|
||||||
log.warn("课程环节不存在,id={}", id);
|
|
||||||
throw new BusinessException("课程环节不存在");
|
|
||||||
}
|
|
||||||
return lesson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按类型查询课程环节
|
* 按类型查询课程环节
|
||||||
*/
|
*/
|
||||||
public CourseLesson findByType(Long courseId, String lessonType) {
|
CourseLesson findByType(Long courseId, String lessonType);
|
||||||
log.info("按类型查询课程环节,courseId={}, lessonType={}", courseId, lessonType);
|
|
||||||
CourseLesson result = courseLessonMapper.selectOne(
|
|
||||||
new LambdaQueryWrapper<CourseLesson>()
|
|
||||||
.eq(CourseLesson::getCourseId, courseId)
|
|
||||||
.eq(CourseLesson::getLessonType, lessonType)
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
log.info("查询课程环节成功,courseId={}, lessonType={}, id={}", courseId, lessonType, result.getId());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建课程环节
|
* 创建课程环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
CourseLesson create(Long courseId, String lessonType, String name, String description,
|
||||||
public CourseLesson create(Long courseId, String lessonType, String name, String description,
|
Integer duration, String videoPath, String videoName,
|
||||||
Integer duration, String videoPath, String videoName,
|
String pptPath, String pptName, String pdfPath, String pdfName,
|
||||||
String pptPath, String pptName, String pdfPath, String pdfName,
|
String objectives, String preparation, String extension,
|
||||||
String objectives, String preparation, String extension,
|
String reflection, String assessmentData, Boolean useTemplate);
|
||||||
String reflection, String assessmentData, Boolean useTemplate) {
|
|
||||||
log.info("创建课程环节,courseId={}, lessonType={}, name={}", courseId, lessonType, name);
|
|
||||||
// 检查是否已存在相同类型的课程
|
|
||||||
CourseLesson existing = findByType(courseId, lessonType);
|
|
||||||
if (existing != null) {
|
|
||||||
log.warn("创建课程环节失败,已存在相同类型的课程环节,courseId={}, lessonType={}", courseId, lessonType);
|
|
||||||
throw new BusinessException("该课程包已存在 " + lessonType + " 类型的课程");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取最大排序号
|
|
||||||
Integer maxSortOrder = courseLessonMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<CourseLesson>()
|
|
||||||
.eq(CourseLesson::getCourseId, courseId)
|
|
||||||
).stream()
|
|
||||||
.map(CourseLesson::getSortOrder)
|
|
||||||
.max(Integer::compareTo)
|
|
||||||
.orElse(0);
|
|
||||||
|
|
||||||
CourseLesson lesson = new CourseLesson();
|
|
||||||
lesson.setCourseId(courseId);
|
|
||||||
lesson.setLessonType(lessonType);
|
|
||||||
lesson.setName(name);
|
|
||||||
lesson.setDescription(description);
|
|
||||||
lesson.setDuration(duration);
|
|
||||||
lesson.setVideoPath(videoPath);
|
|
||||||
lesson.setVideoName(videoName);
|
|
||||||
lesson.setPptPath(pptPath);
|
|
||||||
lesson.setPptName(pptName);
|
|
||||||
lesson.setPdfPath(pdfPath);
|
|
||||||
lesson.setPdfName(pdfName);
|
|
||||||
lesson.setObjectives(objectives);
|
|
||||||
lesson.setPreparation(preparation);
|
|
||||||
lesson.setExtension(extension);
|
|
||||||
lesson.setReflection(reflection);
|
|
||||||
// 确保 assessment_data 为有效 JSON,MySQL JSON 列不接受纯文本
|
|
||||||
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
|
|
||||||
lesson.setUseTemplate(useTemplate);
|
|
||||||
lesson.setSortOrder(maxSortOrder + 1);
|
|
||||||
lesson.setCreatedAt(LocalDateTime.now());
|
|
||||||
|
|
||||||
courseLessonMapper.insert(lesson);
|
|
||||||
log.info("课程环节创建成功,id={}, courseId={}, lessonType={}", lesson.getId(), courseId, lessonType);
|
|
||||||
return lesson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新课程环节
|
* 更新课程环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
CourseLesson update(Long id, String name, String description, Integer duration,
|
||||||
public CourseLesson update(Long id, String name, String description, Integer duration,
|
String videoPath, String videoName, String pptPath, String pptName,
|
||||||
String videoPath, String videoName, String pptPath, String pptName,
|
String pdfPath, String pdfName, String objectives, String preparation,
|
||||||
String pdfPath, String pdfName, String objectives, String preparation,
|
String extension, String reflection, String assessmentData, Boolean useTemplate);
|
||||||
String extension, String reflection, String assessmentData, Boolean useTemplate) {
|
|
||||||
log.info("更新课程环节,id={}", id);
|
|
||||||
CourseLesson lesson = courseLessonMapper.selectById(id);
|
|
||||||
if (lesson == null) {
|
|
||||||
log.warn("课程环节不存在,id={}", id);
|
|
||||||
throw new BusinessException("课程不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
lesson.setName(name);
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
lesson.setDescription(description);
|
|
||||||
}
|
|
||||||
if (duration != null) {
|
|
||||||
lesson.setDuration(duration);
|
|
||||||
}
|
|
||||||
if (videoPath != null) {
|
|
||||||
lesson.setVideoPath(videoPath);
|
|
||||||
}
|
|
||||||
if (videoName != null) {
|
|
||||||
lesson.setVideoName(videoName);
|
|
||||||
}
|
|
||||||
if (pptPath != null) {
|
|
||||||
lesson.setPptPath(pptPath);
|
|
||||||
}
|
|
||||||
if (pptName != null) {
|
|
||||||
lesson.setPptName(pptName);
|
|
||||||
}
|
|
||||||
if (pdfPath != null) {
|
|
||||||
lesson.setPdfPath(pdfPath);
|
|
||||||
}
|
|
||||||
if (pdfName != null) {
|
|
||||||
lesson.setPdfName(pdfName);
|
|
||||||
}
|
|
||||||
if (objectives != null) {
|
|
||||||
lesson.setObjectives(objectives);
|
|
||||||
}
|
|
||||||
if (preparation != null) {
|
|
||||||
lesson.setPreparation(preparation);
|
|
||||||
}
|
|
||||||
if (extension != null) {
|
|
||||||
lesson.setExtension(extension);
|
|
||||||
}
|
|
||||||
if (reflection != null) {
|
|
||||||
lesson.setReflection(reflection);
|
|
||||||
}
|
|
||||||
if (assessmentData != null) {
|
|
||||||
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
|
|
||||||
}
|
|
||||||
if (useTemplate != null) {
|
|
||||||
lesson.setUseTemplate(useTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
lesson.setUpdatedAt(LocalDateTime.now());
|
|
||||||
courseLessonMapper.updateById(lesson);
|
|
||||||
log.info("课程环节更新成功,id={}", id);
|
|
||||||
return lesson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除课程环节
|
* 删除课程环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void delete(Long id);
|
||||||
public void delete(Long id) {
|
|
||||||
log.info("删除课程环节,id={}", id);
|
|
||||||
CourseLesson lesson = courseLessonMapper.selectById(id);
|
|
||||||
if (lesson == null) {
|
|
||||||
log.warn("课程环节不存在,id={}", id);
|
|
||||||
throw new BusinessException("课程不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
courseLessonMapper.deleteById(id);
|
|
||||||
log.info("课程环节删除成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新排序课程环节
|
* 重新排序课程环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void reorder(Long courseId, List<Long> lessonIds);
|
||||||
public void reorder(Long courseId, List<Long> lessonIds) {
|
|
||||||
log.info("重新排序课程环节,courseId={}, count={}", courseId, lessonIds.size());
|
|
||||||
for (int i = 0; i < lessonIds.size(); i++) {
|
|
||||||
CourseLesson lesson = courseLessonMapper.selectById(lessonIds.get(i));
|
|
||||||
if (lesson != null && lesson.getCourseId().equals(courseId)) {
|
|
||||||
lesson.setSortOrder(i + 1);
|
|
||||||
courseLessonMapper.updateById(lesson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("课程环节重新排序成功,courseId={}", courseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询课程环节的教学环节
|
* 查询课程环节的教学环节
|
||||||
*/
|
*/
|
||||||
public List<LessonStep> findSteps(Long lessonId) {
|
List<LessonStep> findSteps(Long lessonId);
|
||||||
log.info("查询课程环节的教学环节,lessonId={}", lessonId);
|
|
||||||
List<LessonStep> result = lessonStepMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<LessonStep>()
|
|
||||||
.eq(LessonStep::getLessonId, lessonId)
|
|
||||||
.orderByAsc(LessonStep::getSortOrder)
|
|
||||||
);
|
|
||||||
log.info("查询教学环节成功,lessonId={}, count={}", lessonId, result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建教学环节
|
* 创建教学环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
LessonStep createStep(Long lessonId, String name, String content, Integer duration,
|
||||||
public LessonStep createStep(Long lessonId, String name, String content, Integer duration,
|
String objective, List<Long> resourceIds);
|
||||||
String objective, List<Long> resourceIds) {
|
|
||||||
log.info("创建教学环节,lessonId={}, name={}", lessonId, name);
|
|
||||||
// 获取最大排序号
|
|
||||||
Integer maxSortOrder = lessonStepMapper.selectList(
|
|
||||||
new LambdaQueryWrapper<LessonStep>()
|
|
||||||
.eq(LessonStep::getLessonId, lessonId)
|
|
||||||
).stream()
|
|
||||||
.map(LessonStep::getSortOrder)
|
|
||||||
.max(Integer::compareTo)
|
|
||||||
.orElse(0);
|
|
||||||
|
|
||||||
LessonStep step = new LessonStep();
|
|
||||||
step.setLessonId(lessonId);
|
|
||||||
step.setName(name);
|
|
||||||
step.setContent(content);
|
|
||||||
step.setDuration(duration != null ? duration : 5);
|
|
||||||
step.setObjective(objective);
|
|
||||||
step.setResourceIds(resourceIds != null ? resourceIds.toString() : null);
|
|
||||||
step.setSortOrder(maxSortOrder + 1);
|
|
||||||
step.setCreatedAt(LocalDateTime.now());
|
|
||||||
|
|
||||||
lessonStepMapper.insert(step);
|
|
||||||
|
|
||||||
// 创建环节资源关联
|
|
||||||
if (resourceIds != null && !resourceIds.isEmpty()) {
|
|
||||||
for (int i = 0; i < resourceIds.size(); i++) {
|
|
||||||
LessonStepResource lsr = new LessonStepResource();
|
|
||||||
lsr.setStepId(step.getId());
|
|
||||||
lsr.setResourceId(resourceIds.get(i));
|
|
||||||
lsr.setSortOrder(i);
|
|
||||||
lessonStepResourceMapper.insert(lsr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("教学环节创建成功,id={}, lessonId={}", step.getId(), lessonId);
|
|
||||||
return step;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新教学环节
|
* 更新教学环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
LessonStep updateStep(Long stepId, String name, String content, Integer duration,
|
||||||
public LessonStep updateStep(Long stepId, String name, String content, Integer duration,
|
String objective, List<Long> resourceIds);
|
||||||
String objective, List<Long> resourceIds) {
|
|
||||||
log.info("更新教学环节,stepId={}", stepId);
|
|
||||||
LessonStep step = lessonStepMapper.selectById(stepId);
|
|
||||||
if (step == null) {
|
|
||||||
log.warn("教学环节不存在,stepId={}", stepId);
|
|
||||||
throw new BusinessException("教学环节不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
step.setName(name);
|
|
||||||
}
|
|
||||||
if (content != null) {
|
|
||||||
step.setContent(content);
|
|
||||||
}
|
|
||||||
if (duration != null) {
|
|
||||||
step.setDuration(duration);
|
|
||||||
}
|
|
||||||
if (objective != null) {
|
|
||||||
step.setObjective(objective);
|
|
||||||
}
|
|
||||||
if (resourceIds != null) {
|
|
||||||
step.setResourceIds(resourceIds.toString());
|
|
||||||
|
|
||||||
// 删除旧的资源关联
|
|
||||||
lessonStepResourceMapper.delete(
|
|
||||||
new LambdaQueryWrapper<LessonStepResource>()
|
|
||||||
.eq(LessonStepResource::getStepId, stepId)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 创建新的资源关联
|
|
||||||
for (int i = 0; i < resourceIds.size(); i++) {
|
|
||||||
LessonStepResource lsr = new LessonStepResource();
|
|
||||||
lsr.setStepId(stepId);
|
|
||||||
lsr.setResourceId(resourceIds.get(i));
|
|
||||||
lsr.setSortOrder(i);
|
|
||||||
lessonStepResourceMapper.insert(lsr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
step.setUpdatedAt(LocalDateTime.now());
|
|
||||||
lessonStepMapper.updateById(step);
|
|
||||||
log.info("教学环节更新成功,stepId={}", stepId);
|
|
||||||
return step;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除教学环节
|
* 删除教学环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void deleteStep(Long stepId);
|
||||||
public void deleteStep(Long stepId) {
|
|
||||||
log.info("删除教学环节,stepId={}", stepId);
|
|
||||||
lessonStepMapper.deleteById(stepId);
|
|
||||||
log.info("教学环节删除成功,stepId={}", stepId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新排序教学环节
|
* 重新排序教学环节
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void reorderSteps(Long lessonId, List<Long> stepIds);
|
||||||
public void reorderSteps(Long lessonId, List<Long> stepIds) {
|
|
||||||
log.info("重新排序教学环节,lessonId={}, count={}", lessonId, stepIds.size());
|
|
||||||
for (int i = 0; i < stepIds.size(); i++) {
|
|
||||||
LessonStep step = lessonStepMapper.selectById(stepIds.get(i));
|
|
||||||
if (step != null && step.getLessonId().equals(lessonId)) {
|
|
||||||
step.setSortOrder(i + 1);
|
|
||||||
lessonStepMapper.updateById(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("教学环节重新排序成功,lessonId={}", lessonId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 assessmentData 转为 MySQL JSON 列可接受的有效 JSON。
|
|
||||||
* 空值返回 null;已是有效 JSON 则原样返回;否则包装为 JSON 字符串。
|
|
||||||
*/
|
|
||||||
private String toValidJsonOrNull(String assessmentData) {
|
|
||||||
if (assessmentData == null || assessmentData.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String trimmed = assessmentData.trim();
|
|
||||||
if (trimmed.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 已是有效 JSON(对象或数组)则直接使用
|
|
||||||
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
||||||
try {
|
|
||||||
JSON.parse(trimmed);
|
|
||||||
return trimmed;
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// 解析失败,当作普通文本处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 普通文本包装为 JSON 字符串
|
|
||||||
return JSON.toJSONString(assessmentData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询教师的课程环节(带权限检查)
|
* 查询教师的课程环节(带权限检查)
|
||||||
*/
|
*/
|
||||||
public List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId) {
|
List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId);
|
||||||
log.info("查询教师的课程环节,courseId={}, tenantId={}", courseId, tenantId);
|
|
||||||
// 检查租户是否有权限访问该课程
|
|
||||||
TenantCourse tenantCourse = tenantCourseMapper.selectOne(
|
|
||||||
new LambdaQueryWrapper<TenantCourse>()
|
|
||||||
.eq(TenantCourse::getTenantId, tenantId)
|
|
||||||
.eq(TenantCourse::getCourseId, courseId)
|
|
||||||
.eq(TenantCourse::getEnabled, 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tenantCourse == null) {
|
|
||||||
// 检查是否通过套餐授权
|
|
||||||
Long count = tenantPackageMapper.selectCount(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getTenantId, tenantId)
|
|
||||||
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
log.warn("无权访问该课程,courseId={}, tenantId={}", courseId, tenantId);
|
|
||||||
throw new BusinessException("无权访问该课程");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CourseLesson> result = findByCourseId(courseId);
|
|
||||||
log.info("查询教师的课程环节成功,courseId={}, count={}", courseId, result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,271 +1,81 @@
|
|||||||
package com.reading.platform.service;
|
package com.reading.platform.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.reading.platform.common.exception.BusinessException;
|
|
||||||
import com.reading.platform.entity.ResourceItem;
|
import com.reading.platform.entity.ResourceItem;
|
||||||
import com.reading.platform.entity.ResourceLibrary;
|
import com.reading.platform.entity.ResourceLibrary;
|
||||||
import com.reading.platform.mapper.ResourceItemMapper;
|
|
||||||
import com.reading.platform.mapper.ResourceLibraryMapper;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源库服务
|
* 资源库服务接口
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface ResourceLibraryService extends IService<ResourceLibrary> {
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ResourceLibraryService extends ServiceImpl<ResourceLibraryMapper, ResourceLibrary> {
|
|
||||||
|
|
||||||
private final ResourceLibraryMapper libraryMapper;
|
|
||||||
private final ResourceItemMapper itemMapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询资源库
|
* 分页查询资源库
|
||||||
*/
|
*/
|
||||||
public Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize) {
|
Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize);
|
||||||
log.info("查询资源库列表,libraryType={}, keyword={}, page={}, pageSize={}", libraryType, keyword, page, pageSize);
|
|
||||||
Page<ResourceLibrary> pageParam = new Page<>(page, pageSize);
|
|
||||||
LambdaQueryWrapper<ResourceLibrary> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
|
|
||||||
if (libraryType != null && !libraryType.isEmpty()) {
|
|
||||||
wrapper.eq(ResourceLibrary::getLibraryType, libraryType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword != null && !keyword.isEmpty()) {
|
|
||||||
wrapper.like(ResourceLibrary::getName, keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.orderByDesc(ResourceLibrary::getCreatedAt);
|
|
||||||
|
|
||||||
Page<ResourceLibrary> result = libraryMapper.selectPage(pageParam, wrapper);
|
|
||||||
log.info("查询资源库列表成功,总数={}", result.getTotal());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询资源库详情
|
* 查询资源库详情
|
||||||
*/
|
*/
|
||||||
public ResourceLibrary findLibraryById(String id) {
|
ResourceLibrary findLibraryById(String id);
|
||||||
log.info("查询资源库详情,id={}", id);
|
|
||||||
ResourceLibrary library = libraryMapper.selectById(id);
|
|
||||||
if (library == null) {
|
|
||||||
log.warn("资源库不存在,id={}", id);
|
|
||||||
throw new BusinessException("资源库不存在");
|
|
||||||
}
|
|
||||||
return library;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询资源库(不存在时返回 null)
|
* 查询资源库(不存在时返回 null)
|
||||||
*/
|
*/
|
||||||
public ResourceLibrary findLibraryByIdOrNull(String id) {
|
ResourceLibrary findLibraryByIdOrNull(String id);
|
||||||
if (id == null || id.isEmpty()) return null;
|
|
||||||
return libraryMapper.selectById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建资源库
|
* 创建资源库
|
||||||
*/
|
*/
|
||||||
public ResourceLibrary createLibrary(String name, String type, String description, String tenantId) {
|
ResourceLibrary createLibrary(String name, String type, String description, String tenantId);
|
||||||
log.info("创建资源库,name={}, type={}, tenantId={}", name, type, tenantId);
|
|
||||||
ResourceLibrary library = new ResourceLibrary();
|
|
||||||
library.setName(name);
|
|
||||||
library.setLibraryType(type);
|
|
||||||
library.setDescription(description);
|
|
||||||
library.setTenantId(tenantId);
|
|
||||||
library.setStatus("ACTIVE");
|
|
||||||
library.setSortOrder(0);
|
|
||||||
|
|
||||||
libraryMapper.insert(library);
|
|
||||||
log.info("资源库创建成功,id={}", library.getId());
|
|
||||||
return library;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新资源库
|
* 更新资源库
|
||||||
*/
|
*/
|
||||||
public ResourceLibrary updateLibrary(String id, String name, String description) {
|
ResourceLibrary updateLibrary(String id, String name, String description);
|
||||||
log.info("更新资源库,id={}, name={}, description={}", id, name, description);
|
|
||||||
ResourceLibrary library = libraryMapper.selectById(id);
|
|
||||||
if (library == null) {
|
|
||||||
log.warn("资源库不存在,id={}", id);
|
|
||||||
throw new BusinessException("资源库不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
library.setName(name);
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
library.setDescription(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryMapper.updateById(library);
|
|
||||||
log.info("资源库更新成功,id={}", id);
|
|
||||||
return library;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除资源库
|
* 删除资源库
|
||||||
*/
|
*/
|
||||||
public void deleteLibrary(String id) {
|
void deleteLibrary(String id);
|
||||||
log.info("删除资源库,id={}", id);
|
|
||||||
// 先删除该资源库下的所有资源项
|
|
||||||
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(ResourceItem::getLibraryId, id);
|
|
||||||
itemMapper.delete(wrapper);
|
|
||||||
|
|
||||||
libraryMapper.deleteById(id);
|
|
||||||
log.info("资源库删除成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询资源项目
|
* 分页查询资源项目
|
||||||
*/
|
*/
|
||||||
public Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize) {
|
Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize);
|
||||||
log.info("查询资源项目列表,libraryId={}, fileType={}, keyword={}, page={}, pageSize={}", libraryId, fileType, keyword, page, pageSize);
|
|
||||||
Page<ResourceItem> pageParam = new Page<>(page, pageSize);
|
|
||||||
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
|
|
||||||
if (libraryId != null && !libraryId.isEmpty()) {
|
|
||||||
wrapper.eq(ResourceItem::getLibraryId, libraryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileType != null && !fileType.isEmpty()) {
|
|
||||||
wrapper.eq(ResourceItem::getFileType, fileType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword != null && !keyword.isEmpty()) {
|
|
||||||
wrapper.and(w -> w.like(ResourceItem::getTitle, keyword)
|
|
||||||
.or()
|
|
||||||
.like(ResourceItem::getDescription, keyword));
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.orderByDesc(ResourceItem::getCreatedAt);
|
|
||||||
|
|
||||||
Page<ResourceItem> result = itemMapper.selectPage(pageParam, wrapper);
|
|
||||||
log.info("查询资源项目列表成功,总数={}", result.getTotal());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询资源项目详情
|
* 查询资源项目详情
|
||||||
*/
|
*/
|
||||||
public ResourceItem findItemById(String id) {
|
ResourceItem findItemById(String id);
|
||||||
log.info("查询资源项目详情,id={}", id);
|
|
||||||
ResourceItem item = itemMapper.selectById(id);
|
|
||||||
if (item == null) {
|
|
||||||
log.warn("资源项目不存在,id={}", id);
|
|
||||||
throw new BusinessException("资源项目不存在");
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建资源项目(数字资源)
|
* 创建资源项目(数字资源)
|
||||||
*/
|
*/
|
||||||
public ResourceItem createItem(String libraryId, String title, String fileType, String filePath,
|
ResourceItem createItem(String libraryId, String title, String fileType, String filePath,
|
||||||
Long fileSize, String description, String tags, String tenantId) {
|
Long fileSize, String description, String tags, String tenantId);
|
||||||
log.info("创建资源项目,libraryId={}, title={}, fileType={}, fileSize={}, tenantId={}", libraryId, title, fileType, fileSize, tenantId);
|
|
||||||
ResourceItem item = new ResourceItem();
|
|
||||||
item.setId(null); // 确保由数据库 AUTO_INCREMENT 生成,避免主键冲突
|
|
||||||
item.setLibraryId(libraryId); // 目标资源库 ID
|
|
||||||
item.setTitle(title);
|
|
||||||
item.setName(title); // 数据库 name 字段 NOT NULL,与 title 保持一致
|
|
||||||
item.setDescription(description);
|
|
||||||
item.setFileType(fileType);
|
|
||||||
item.setFilePath(filePath);
|
|
||||||
item.setFileSize(fileSize);
|
|
||||||
item.setTags(tags);
|
|
||||||
item.setSortOrder(0);
|
|
||||||
item.setTenantId(tenantId);
|
|
||||||
item.setStatus("ACTIVE");
|
|
||||||
|
|
||||||
itemMapper.insert(item);
|
|
||||||
log.info("资源项目创建成功,id={}", item.getId());
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新资源项目(数字资源)
|
* 更新资源项目(数字资源)
|
||||||
*/
|
*/
|
||||||
public ResourceItem updateItem(String id, String title, String description, String tags) {
|
ResourceItem updateItem(String id, String title, String description, String tags);
|
||||||
log.info("更新资源项目,id={}, title={}, description={}, tags={}", id, title, description, tags);
|
|
||||||
ResourceItem item = itemMapper.selectById(id);
|
|
||||||
if (item == null) {
|
|
||||||
log.warn("资源项目不存在,id={}", id);
|
|
||||||
throw new BusinessException("资源项目不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title != null) {
|
|
||||||
item.setTitle(title);
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
item.setDescription(description);
|
|
||||||
}
|
|
||||||
if (tags != null) {
|
|
||||||
item.setTags(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
itemMapper.updateById(item);
|
|
||||||
log.info("资源项目更新成功,id={}", id);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除资源项目
|
* 删除资源项目
|
||||||
*/
|
*/
|
||||||
public void deleteItem(String id) {
|
void deleteItem(String id);
|
||||||
log.info("删除资源项目,id={}", id);
|
|
||||||
itemMapper.deleteById(id);
|
|
||||||
log.info("资源项目删除成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除资源项目
|
* 批量删除资源项目
|
||||||
*/
|
*/
|
||||||
public void batchDeleteItems(List<String> ids) {
|
void batchDeleteItems(List<String> ids);
|
||||||
log.info("批量删除资源项目,ids={}", ids);
|
|
||||||
itemMapper.deleteBatchIds(ids);
|
|
||||||
log.info("批量删除资源项目成功,数量={}", ids.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取统计数据
|
* 获取统计数据
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> getStats() {
|
Map<String, Object> getStats();
|
||||||
log.info("获取资源库统计数据");
|
|
||||||
Map<String, Object> stats = new HashMap<>();
|
|
||||||
|
|
||||||
Long libraryCount = libraryMapper.selectCount(null);
|
|
||||||
Long itemCount = itemMapper.selectCount(null);
|
|
||||||
|
|
||||||
stats.put("totalLibraries", libraryCount);
|
|
||||||
stats.put("totalItems", itemCount);
|
|
||||||
|
|
||||||
// 按资源库类型统计资源数量(绘本资源、教学材料等)
|
|
||||||
Map<String, Long> itemsByLibraryType = new HashMap<>();
|
|
||||||
LambdaQueryWrapper<ResourceLibrary> libWrapper = new LambdaQueryWrapper<>();
|
|
||||||
for (ResourceLibrary lib : libraryMapper.selectList(libWrapper)) {
|
|
||||||
String type = lib.getLibraryType();
|
|
||||||
if (type != null) {
|
|
||||||
LambdaQueryWrapper<ResourceItem> itemWrapper = new LambdaQueryWrapper<>();
|
|
||||||
itemWrapper.eq(ResourceItem::getLibraryId, String.valueOf(lib.getId()));
|
|
||||||
long count = itemMapper.selectCount(itemWrapper);
|
|
||||||
itemsByLibraryType.merge(type, count, Long::sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stats.put("itemsByLibraryType", itemsByLibraryType);
|
|
||||||
|
|
||||||
log.info("资源库统计数据:totalLibraries={}, totalItems={}, itemsByLibraryType={}", libraryCount, itemCount, itemsByLibraryType);
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,142 +1,42 @@
|
|||||||
package com.reading.platform.service;
|
package com.reading.platform.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import com.reading.platform.common.exception.BusinessException;
|
|
||||||
import com.reading.platform.entity.CoursePackage;
|
|
||||||
import com.reading.platform.entity.Theme;
|
import com.reading.platform.entity.Theme;
|
||||||
import com.reading.platform.mapper.CoursePackageMapper;
|
|
||||||
import com.reading.platform.mapper.ThemeMapper;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题字典服务
|
* 主题字典服务接口
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface ThemeService extends IService<Theme> {
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ThemeService extends ServiceImpl<ThemeMapper, Theme> {
|
|
||||||
|
|
||||||
private final ThemeMapper themeMapper;
|
|
||||||
private final CoursePackageMapper courseMapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询所有主题
|
* 查询所有主题
|
||||||
*/
|
*/
|
||||||
public List<Theme> findAll() {
|
List<Theme> findAll();
|
||||||
log.info("查询所有主题");
|
|
||||||
List<Theme> result = lambdaQuery()
|
|
||||||
.orderByAsc(Theme::getSortOrder)
|
|
||||||
.list();
|
|
||||||
log.info("查询所有主题成功,count={}", result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询主题详情
|
* 查询主题详情
|
||||||
*/
|
*/
|
||||||
public Theme findById(Long id) {
|
Theme findById(Long id);
|
||||||
log.info("查询主题详情,id={}", id);
|
|
||||||
Theme theme = themeMapper.selectById(id);
|
|
||||||
if (theme == null) {
|
|
||||||
log.warn("主题不存在,id={}", id);
|
|
||||||
throw new BusinessException("主题不存在");
|
|
||||||
}
|
|
||||||
return theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建主题
|
* 创建主题
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
Theme create(String name, String description, Integer sortOrder);
|
||||||
public Theme create(String name, String description, Integer sortOrder) {
|
|
||||||
log.info("创建主题,name={}, sortOrder={}", name, sortOrder);
|
|
||||||
// 获取最大排序号
|
|
||||||
Integer maxSortOrder = themeMapper.selectList(null)
|
|
||||||
.stream()
|
|
||||||
.map(Theme::getSortOrder)
|
|
||||||
.max(Integer::compareTo)
|
|
||||||
.orElse(0);
|
|
||||||
|
|
||||||
Theme theme = new Theme();
|
|
||||||
theme.setName(name);
|
|
||||||
theme.setDescription(description);
|
|
||||||
theme.setSortOrder(sortOrder != null ? sortOrder : maxSortOrder + 1);
|
|
||||||
theme.setStatus("ACTIVE");
|
|
||||||
themeMapper.insert(theme);
|
|
||||||
|
|
||||||
log.info("主题创建成功,id={}", theme.getId());
|
|
||||||
return theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新主题
|
* 更新主题
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
Theme update(Long id, String name, String description, Integer sortOrder, String status);
|
||||||
public Theme update(Long id, String name, String description, Integer sortOrder, String status) {
|
|
||||||
log.info("更新主题,id={}, name={}, status={}", id, name, status);
|
|
||||||
Theme theme = themeMapper.selectById(id);
|
|
||||||
if (theme == null) {
|
|
||||||
log.warn("主题不存在,id={}", id);
|
|
||||||
throw new BusinessException("主题不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
theme.setName(name);
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
theme.setDescription(description);
|
|
||||||
}
|
|
||||||
if (sortOrder != null) {
|
|
||||||
theme.setSortOrder(sortOrder);
|
|
||||||
}
|
|
||||||
if (status != null) {
|
|
||||||
theme.setStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
themeMapper.updateById(theme);
|
|
||||||
log.info("主题更新成功,id={}", id);
|
|
||||||
return theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除主题
|
* 删除主题
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void delete(Long id);
|
||||||
public void delete(Long id) {
|
|
||||||
log.info("删除主题,id={}", id);
|
|
||||||
// 检查是否有关联课程
|
|
||||||
Long courseCount = courseMapper.selectCount(
|
|
||||||
new LambdaQueryWrapper<CoursePackage>().eq(CoursePackage::getThemeId, id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (courseCount > 0) {
|
|
||||||
log.warn("删除主题失败,该主题下有 {} 个课程包,id={}", courseCount, id);
|
|
||||||
throw new BusinessException("该主题下有 " + courseCount + " 个课程包,无法删除");
|
|
||||||
}
|
|
||||||
|
|
||||||
themeMapper.deleteById(id);
|
|
||||||
log.info("主题删除成功,id={}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新排序
|
* 重新排序
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
void reorder(List<Long> ids);
|
||||||
public void reorder(List<Long> ids) {
|
|
||||||
log.info("重新排序主题,count={}", ids.size());
|
|
||||||
for (int i = 0; i < ids.size(); i++) {
|
|
||||||
Theme theme = themeMapper.selectById(ids.get(i));
|
|
||||||
if (theme != null) {
|
|
||||||
theme.setSortOrder(i + 1);
|
|
||||||
themeMapper.updateById(theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("主题重新排序成功");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,633 @@
|
|||||||
|
package com.reading.platform.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.reading.platform.common.enums.CourseStatus;
|
||||||
|
import com.reading.platform.common.enums.TenantPackageStatus;
|
||||||
|
import com.reading.platform.common.exception.BusinessException;
|
||||||
|
import com.reading.platform.common.response.PageResult;
|
||||||
|
import com.reading.platform.dto.response.CourseCollectionResponse;
|
||||||
|
import com.reading.platform.dto.response.CoursePackageResponse;
|
||||||
|
import com.reading.platform.entity.*;
|
||||||
|
import com.reading.platform.mapper.*;
|
||||||
|
import com.reading.platform.service.CourseCollectionService;
|
||||||
|
import com.reading.platform.service.CourseLessonService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课程套餐服务实现类(两层结构 - 最上层)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMapper, CourseCollection> implements CourseCollectionService {
|
||||||
|
|
||||||
|
private final CourseCollectionMapper collectionMapper;
|
||||||
|
private final CourseCollectionPackageMapper collectionPackageMapper;
|
||||||
|
private final CoursePackageMapper packageMapper;
|
||||||
|
private final TenantPackageMapper tenantPackageMapper;
|
||||||
|
private final CoursePackageCourseMapper packageCoursePackageMapper;
|
||||||
|
private final CoursePackageMapper courseMapper;
|
||||||
|
private final CourseLessonService courseLessonService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询租户的课程套餐列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CourseCollectionResponse> findTenantCollections(Long tenantId) {
|
||||||
|
log.info("查询租户课程套餐,tenantId={}", tenantId);
|
||||||
|
|
||||||
|
// 查询租户的课程套餐关联
|
||||||
|
List<TenantPackage> tenantPackages = tenantPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getTenantId, tenantId)
|
||||||
|
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE.getCode())
|
||||||
|
.isNotNull(TenantPackage::getCollectionId)
|
||||||
|
.orderByDesc(TenantPackage::getCreatedAt)
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info("查询到{}条租户套餐关联记录", tenantPackages.size());
|
||||||
|
for (TenantPackage tp : tenantPackages) {
|
||||||
|
log.info(" - collection_id: {}, start_date: {}, end_date: {}",
|
||||||
|
tp.getCollectionId(), tp.getStartDate(), tp.getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取套餐详情并转换为响应对象
|
||||||
|
List<CourseCollectionResponse> result = tenantPackages.stream()
|
||||||
|
.map(tp -> {
|
||||||
|
CourseCollection collection = collectionMapper.selectById(tp.getCollectionId());
|
||||||
|
if (collection == null) {
|
||||||
|
log.warn("套餐不存在,collection_id={}", tp.getCollectionId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 过滤下架状态的课程套餐
|
||||||
|
if (CourseStatus.ARCHIVED.getCode().equals(collection.getStatus())) {
|
||||||
|
log.warn("套餐已下架,collection_id={}", tp.getCollectionId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
log.info("返回套餐:id={}, name={}, package_count={}",
|
||||||
|
collection.getId(), collection.getName(), collection.getPackageCount());
|
||||||
|
CourseCollectionResponse response = toResponse(collection);
|
||||||
|
// 设置租户套餐的额外信息(如果有)
|
||||||
|
response.setStartDate(tp.getStartDate());
|
||||||
|
response.setEndDate(tp.getEndDate());
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.filter(r -> r != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("查询到{}个课程套餐", result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程套餐详情(包含课程包列表)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CourseCollectionResponse getCollectionDetail(Long collectionId) {
|
||||||
|
log.info("获取课程套餐详情,collectionId={}", collectionId);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(collectionId);
|
||||||
|
if (collection == null) {
|
||||||
|
log.warn("课程套餐不存在,collectionId={}", collectionId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toResponse(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status) {
|
||||||
|
log.info("分页查询课程套餐,pageNum={}, pageSize={}, status={}", pageNum, pageSize, status);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<CourseCollection> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (StringUtils.hasText(status)) {
|
||||||
|
wrapper.eq(CourseCollection::getStatus, status);
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(CourseCollection::getCreatedAt);
|
||||||
|
|
||||||
|
Page<CourseCollection> page = collectionMapper.selectPage(
|
||||||
|
new Page<>(pageNum, pageSize),
|
||||||
|
wrapper
|
||||||
|
);
|
||||||
|
|
||||||
|
// 转换为响应对象
|
||||||
|
List<CourseCollectionResponse> responses = page.getRecords().stream()
|
||||||
|
.map(this::toResponse)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Page<CourseCollectionResponse> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||||
|
result.setRecords(responses);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程套餐下的课程包列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CoursePackageResponse> getPackagesByCollection(Long collectionId) {
|
||||||
|
log.info("获取课程套餐的课程包列表,collectionId={}", collectionId);
|
||||||
|
|
||||||
|
// 查询关联关系
|
||||||
|
List<CourseCollectionPackage> associations = collectionPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CourseCollectionPackage>()
|
||||||
|
.eq(CourseCollectionPackage::getCollectionId, collectionId)
|
||||||
|
.orderByAsc(CourseCollectionPackage::getSortOrder)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (associations.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取课程包详情
|
||||||
|
List<Long> packageIds = associations.stream()
|
||||||
|
.map(CourseCollectionPackage::getPackageId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<CoursePackage> packages = packageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CoursePackage>()
|
||||||
|
.in(CoursePackage::getId, packageIds)
|
||||||
|
.eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 转换为响应对象并设置排序号
|
||||||
|
List<CoursePackageResponse> result = packages.stream()
|
||||||
|
.map(pkg -> {
|
||||||
|
CoursePackageResponse response = toPackageResponse(pkg);
|
||||||
|
// 设置排序号
|
||||||
|
associations.stream()
|
||||||
|
.filter(a -> a.getPackageId().equals(pkg.getId()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(a -> response.setSortOrder(a.getSortOrder()));
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
log.info("查询到{}个课程包", result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public CourseCollectionResponse createCollection(String name, String description, Long price,
|
||||||
|
Long discountPrice, String discountType, String[] gradeLevels) {
|
||||||
|
log.info("创建课程套餐,name={}", name);
|
||||||
|
|
||||||
|
CourseCollection collection = new CourseCollection();
|
||||||
|
collection.setName(name);
|
||||||
|
collection.setDescription(description);
|
||||||
|
collection.setPrice(price);
|
||||||
|
collection.setDiscountPrice(discountPrice);
|
||||||
|
collection.setDiscountType(discountType);
|
||||||
|
// 将数组转为 JSON 字符串存储到 JSON 字段
|
||||||
|
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
|
||||||
|
collection.setPackageCount(0);
|
||||||
|
collection.setStatus(CourseStatus.DRAFT.getCode());
|
||||||
|
|
||||||
|
collectionMapper.insert(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐创建成功,id={}", collection.getId());
|
||||||
|
return toResponse(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置课程套餐的课程包
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void setCollectionPackages(Long collectionId, List<Long> packageIds) {
|
||||||
|
log.info("设置课程套餐的课程包,collectionId={}, packageCount={}", collectionId, packageIds.size());
|
||||||
|
|
||||||
|
// 验证课程套餐是否存在
|
||||||
|
CourseCollection collection = collectionMapper.selectById(collectionId);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new BusinessException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去重:保持首次出现顺序,避免 uk_collection_package 唯一约束冲突
|
||||||
|
List<Long> distinctIds = packageIds.stream().distinct().collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 验证课程包是否存在(应用层外键约束)
|
||||||
|
if (!distinctIds.isEmpty()) {
|
||||||
|
List<CoursePackage> packages = packageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CoursePackage>()
|
||||||
|
.in(CoursePackage::getId, distinctIds)
|
||||||
|
);
|
||||||
|
if (packages.size() != distinctIds.size()) {
|
||||||
|
throw new BusinessException("存在无效的课程包 ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除旧的关联
|
||||||
|
collectionPackageMapper.delete(
|
||||||
|
new LambdaQueryWrapper<CourseCollectionPackage>()
|
||||||
|
.eq(CourseCollectionPackage::getCollectionId, collectionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建新的关联(在事务内顺序插入)
|
||||||
|
for (int i = 0; i < distinctIds.size(); i++) {
|
||||||
|
CourseCollectionPackage association = new CourseCollectionPackage();
|
||||||
|
association.setCollectionId(collectionId);
|
||||||
|
association.setPackageId(distinctIds.get(i));
|
||||||
|
association.setSortOrder(i + 1);
|
||||||
|
collectionPackageMapper.insert(association);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新课程包数量(复用前面已验证的 collection 变量)
|
||||||
|
collection.setPackageCount(distinctIds.size());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐的课程包设置完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public CourseCollectionResponse updateCollection(Long id, String name, String description, Long price,
|
||||||
|
Long discountPrice, String discountType, String[] gradeLevels) {
|
||||||
|
log.info("更新课程套餐,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setName(name);
|
||||||
|
collection.setDescription(description);
|
||||||
|
collection.setPrice(price);
|
||||||
|
collection.setDiscountPrice(discountPrice);
|
||||||
|
collection.setDiscountType(discountType);
|
||||||
|
// 将数组转为 JSON 字符串存储到 JSON 字段
|
||||||
|
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
|
||||||
|
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐更新成功,id={}", id);
|
||||||
|
return toResponse(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteCollection(Long id) {
|
||||||
|
log.info("删除课程套餐,id={}", id);
|
||||||
|
|
||||||
|
// 检查是否有租户正在使用此课程套餐
|
||||||
|
Long tenantCount = tenantPackageMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getCollectionId, id)
|
||||||
|
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tenantCount > 0) {
|
||||||
|
log.warn("删除课程套餐失败,有 {} 个租户正在使用此套餐,id={}", tenantCount, id);
|
||||||
|
throw new BusinessException("有 " + tenantCount + " 个租户正在使用此课程套餐,无法删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理所有状态的租户套餐关联记录(包括非活跃状态)
|
||||||
|
List<TenantPackage> allTenantPackages = tenantPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getCollectionId, id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allTenantPackages.isEmpty()) {
|
||||||
|
tenantPackageMapper.delete(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getCollectionId, id)
|
||||||
|
);
|
||||||
|
log.info("已清理 {} 条租户套餐关联记录", allTenantPackages.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除课程套餐与课程包的关联关系
|
||||||
|
List<CourseCollectionPackage> collectionPackages = collectionPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CourseCollectionPackage>()
|
||||||
|
.eq(CourseCollectionPackage::getCollectionId, id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!collectionPackages.isEmpty()) {
|
||||||
|
collectionPackageMapper.delete(
|
||||||
|
new LambdaQueryWrapper<CourseCollectionPackage>()
|
||||||
|
.eq(CourseCollectionPackage::getCollectionId, id)
|
||||||
|
);
|
||||||
|
log.info("已清理 {} 条课程包关联记录", collectionPackages.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除课程套餐
|
||||||
|
collectionMapper.deleteById(id);
|
||||||
|
|
||||||
|
log.info("课程套餐删除成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void publishCollection(Long id) {
|
||||||
|
log.info("发布课程套餐,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.PUBLISHED.getCode());
|
||||||
|
collection.setPublishedAt(LocalDateTime.now());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐发布成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下架课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void archiveCollection(Long id) {
|
||||||
|
log.info("下架课程套餐,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.ARCHIVED.getCode());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐下架成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新发布课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void republishCollection(Long id) {
|
||||||
|
log.info("重新发布课程套餐,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.PUBLISHED.getCode());
|
||||||
|
collection.setPublishedAt(LocalDateTime.now());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐重新发布成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤销审核
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void withdrawCollection(Long id) {
|
||||||
|
log.info("撤销课程套餐审核,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.DRAFT.getCode());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐审核撤销成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交课程套餐审核
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void submitCollection(Long id) {
|
||||||
|
log.info("提交课程套餐审核,id={}", id);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含课程包
|
||||||
|
if (collection.getPackageCount() == null || collection.getPackageCount() == 0) {
|
||||||
|
throw new BusinessException("课程套餐必须包含至少一个课程包");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.PENDING.getCode());
|
||||||
|
collection.setSubmittedAt(LocalDateTime.now());
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐提交审核成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核驳回课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void rejectCollection(Long id, String comment) {
|
||||||
|
log.info("审核驳回课程套餐,id={}, comment={}", id, comment);
|
||||||
|
|
||||||
|
CourseCollection collection = collectionMapper.selectById(id);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.setStatus(CourseStatus.REJECTED.getCode());
|
||||||
|
collection.setReviewedAt(LocalDateTime.now());
|
||||||
|
collection.setReviewComment(comment);
|
||||||
|
collectionMapper.updateById(collection);
|
||||||
|
|
||||||
|
log.info("课程套餐审核驳回成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 续费租户课程套餐
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void renewTenantCollection(Long tenantId, Long collectionId, java.time.LocalDate endDate, Long pricePaid) {
|
||||||
|
log.info("续费租户课程套餐,tenantId={}, collectionId={}, endDate={}, pricePaid={}", tenantId, collectionId, endDate, pricePaid);
|
||||||
|
|
||||||
|
// 查询现有租户套餐关联
|
||||||
|
List<TenantPackage> existingPackages = tenantPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getTenantId, tenantId)
|
||||||
|
.eq(TenantPackage::getCollectionId, collectionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existingPackages.isEmpty()) {
|
||||||
|
// 更新现有记录
|
||||||
|
TenantPackage existing = existingPackages.get(0);
|
||||||
|
existing.setEndDate(endDate);
|
||||||
|
existing.setStatus(TenantPackageStatus.ACTIVE);
|
||||||
|
if (pricePaid != null) {
|
||||||
|
existing.setPricePaid(pricePaid);
|
||||||
|
}
|
||||||
|
existing.setUpdatedAt(LocalDateTime.now());
|
||||||
|
tenantPackageMapper.updateById(existing);
|
||||||
|
log.info("租户课程套餐续费成功,tenantId={}, collectionId={}", tenantId, collectionId);
|
||||||
|
} else {
|
||||||
|
// 查询课程套餐信息
|
||||||
|
CourseCollection collection = collectionMapper.selectById(collectionId);
|
||||||
|
if (collection == null) {
|
||||||
|
throw new IllegalArgumentException("课程套餐不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新记录
|
||||||
|
TenantPackage tp = new TenantPackage();
|
||||||
|
tp.setTenantId(tenantId);
|
||||||
|
tp.setCollectionId(collectionId);
|
||||||
|
tp.setStartDate(java.time.LocalDate.now());
|
||||||
|
tp.setEndDate(endDate);
|
||||||
|
tp.setStatus(TenantPackageStatus.ACTIVE);
|
||||||
|
tp.setPricePaid(pricePaid != null ? pricePaid :
|
||||||
|
(collection.getDiscountPrice() != null ? collection.getDiscountPrice() : collection.getPrice()));
|
||||||
|
tp.setCreatedAt(LocalDateTime.now());
|
||||||
|
tenantPackageMapper.insert(tp);
|
||||||
|
log.info("租户课程套餐新办成功,tenantId={}, collectionId={}", tenantId, collectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析适用年级:数据库存储为 JSON 数组 ["小班","中班","大班"],需正确解析
|
||||||
|
*/
|
||||||
|
private String[] parseGradeLevels(String gradeLevels) {
|
||||||
|
if (!StringUtils.hasText(gradeLevels)) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (gradeLevels.trim().startsWith("[")) {
|
||||||
|
return JSON.parseArray(gradeLevels, String.class).toArray(new String[0]);
|
||||||
|
}
|
||||||
|
// 兼容旧数据:逗号分隔格式
|
||||||
|
return gradeLevels.split(",");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析 gradeLevels 失败:{}", gradeLevels, e);
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为响应对象
|
||||||
|
*/
|
||||||
|
private CourseCollectionResponse toResponse(CourseCollection collection) {
|
||||||
|
// 获取课程包列表
|
||||||
|
List<CourseCollectionResponse.CoursePackageItem> packages = getPackagesByCollection(collection.getId()).stream()
|
||||||
|
.map(pkg -> {
|
||||||
|
CourseCollectionResponse.CoursePackageItem item = new CourseCollectionResponse.CoursePackageItem();
|
||||||
|
item.setId(pkg.getId());
|
||||||
|
item.setName(pkg.getName());
|
||||||
|
item.setDescription(pkg.getDescription());
|
||||||
|
item.setGradeLevels(pkg.getGradeLevels() != null ? pkg.getGradeLevels() : new String[0]);
|
||||||
|
item.setCourseCount(pkg.getCourseCount() != null ? pkg.getCourseCount() : 0);
|
||||||
|
item.setSortOrder(pkg.getSortOrder());
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return CourseCollectionResponse.builder()
|
||||||
|
.id(collection.getId())
|
||||||
|
.name(collection.getName())
|
||||||
|
.description(collection.getDescription())
|
||||||
|
.price(collection.getPrice())
|
||||||
|
.discountPrice(collection.getDiscountPrice())
|
||||||
|
.discountType(collection.getDiscountType())
|
||||||
|
.gradeLevels(parseGradeLevels(collection.getGradeLevels()))
|
||||||
|
.packageCount(collection.getPackageCount())
|
||||||
|
.status(collection.getStatus())
|
||||||
|
.submittedAt(collection.getSubmittedAt())
|
||||||
|
.submittedBy(collection.getSubmittedBy())
|
||||||
|
.reviewedAt(collection.getReviewedAt())
|
||||||
|
.reviewedBy(collection.getReviewedBy())
|
||||||
|
.reviewComment(collection.getReviewComment())
|
||||||
|
.publishedAt(collection.getPublishedAt())
|
||||||
|
.createdAt(collection.getCreatedAt())
|
||||||
|
.updatedAt(collection.getUpdatedAt())
|
||||||
|
.packages(packages)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为课程包响应对象(包含课程列表和排课计划参考)
|
||||||
|
*/
|
||||||
|
private CoursePackageResponse toPackageResponse(CoursePackage pkg) {
|
||||||
|
// 解析 gradeTags(CoursePackage 使用 gradeTags)
|
||||||
|
String[] gradeLevelsArray = new String[0];
|
||||||
|
if (pkg.getGradeTags() != null && !pkg.getGradeTags().isEmpty()) {
|
||||||
|
try {
|
||||||
|
// gradeTags 是 JSON 数组格式,尝试解析
|
||||||
|
if (pkg.getGradeTags().startsWith("[")) {
|
||||||
|
gradeLevelsArray = com.alibaba.fastjson2.JSON.parseArray(pkg.getGradeTags(), String.class).toArray(new String[0]);
|
||||||
|
} else {
|
||||||
|
gradeLevelsArray = pkg.getGradeTags().split(",");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
gradeLevelsArray = new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CoursePackageResponse.CoursePackageCourseItem> courseItems = new ArrayList<>();
|
||||||
|
|
||||||
|
// 查询课程包关联的课程环节
|
||||||
|
List<CourseLesson> lessons = courseLessonService.findByCourseId(pkg.getId());
|
||||||
|
|
||||||
|
if (!lessons.isEmpty()) {
|
||||||
|
courseItems = lessons.stream()
|
||||||
|
.map(lesson -> {
|
||||||
|
CoursePackageResponse.CoursePackageCourseItem item = new CoursePackageResponse.CoursePackageCourseItem();
|
||||||
|
item.setId(lesson.getId());
|
||||||
|
item.setName(lesson.getName());
|
||||||
|
item.setGradeLevel(null); // 课程环节没有年级字段
|
||||||
|
item.setSortOrder(lesson.getSortOrder());
|
||||||
|
item.setScheduleRefData(pkg.getScheduleRefData());
|
||||||
|
item.setLessonType(lesson.getLessonType());
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoursePackageResponse.builder()
|
||||||
|
.id(pkg.getId())
|
||||||
|
.name(pkg.getName())
|
||||||
|
.description(pkg.getDescription())
|
||||||
|
.price(null) // CoursePackage 没有价格字段
|
||||||
|
.discountPrice(null)
|
||||||
|
.discountType(null)
|
||||||
|
.gradeLevels(gradeLevelsArray)
|
||||||
|
.courseCount(lessons.size()) // 使用课程环节数量
|
||||||
|
.status(pkg.getStatus())
|
||||||
|
.submittedAt(pkg.getSubmittedAt())
|
||||||
|
.submittedBy(pkg.getSubmittedBy())
|
||||||
|
.reviewedAt(pkg.getReviewedAt())
|
||||||
|
.reviewedBy(pkg.getReviewedBy())
|
||||||
|
.reviewComment(pkg.getReviewComment())
|
||||||
|
.publishedAt(pkg.getPublishedAt())
|
||||||
|
.createdAt(pkg.getCreatedAt())
|
||||||
|
.updatedAt(pkg.getUpdatedAt())
|
||||||
|
.courses(courseItems)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,430 @@
|
|||||||
|
package com.reading.platform.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.reading.platform.common.enums.TenantPackageStatus;
|
||||||
|
import com.reading.platform.common.exception.BusinessException;
|
||||||
|
import com.reading.platform.entity.*;
|
||||||
|
import com.reading.platform.mapper.*;
|
||||||
|
import com.reading.platform.service.CourseLessonService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课程环节服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CourseLessonServiceImpl extends ServiceImpl<CourseLessonMapper, CourseLesson> implements CourseLessonService {
|
||||||
|
|
||||||
|
private final CourseLessonMapper courseLessonMapper;
|
||||||
|
private final LessonStepMapper lessonStepMapper;
|
||||||
|
private final LessonStepResourceMapper lessonStepResourceMapper;
|
||||||
|
private final TenantCourseMapper tenantCourseMapper;
|
||||||
|
private final TenantPackageMapper tenantPackageMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询课程的所有环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CourseLesson> findByCourseId(Long courseId) {
|
||||||
|
log.info("查询课程的所有环节,courseId={}", courseId);
|
||||||
|
List<CourseLesson> result = courseLessonMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CourseLesson>()
|
||||||
|
.eq(CourseLesson::getCourseId, courseId)
|
||||||
|
.orderByAsc(CourseLesson::getSortOrder)
|
||||||
|
);
|
||||||
|
log.info("查询课程环节成功,courseId={}, count={}", courseId, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询课程环节详情
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CourseLesson findById(Long id) {
|
||||||
|
log.info("查询课程环节详情,id={}", id);
|
||||||
|
CourseLesson lesson = courseLessonMapper.selectById(id);
|
||||||
|
if (lesson == null) {
|
||||||
|
log.warn("课程环节不存在,id={}", id);
|
||||||
|
throw new BusinessException("课程环节不存在");
|
||||||
|
}
|
||||||
|
return lesson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型查询课程环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CourseLesson findByType(Long courseId, String lessonType) {
|
||||||
|
log.info("按类型查询课程环节,courseId={}, lessonType={}", courseId, lessonType);
|
||||||
|
CourseLesson result = courseLessonMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<CourseLesson>()
|
||||||
|
.eq(CourseLesson::getCourseId, courseId)
|
||||||
|
.eq(CourseLesson::getLessonType, lessonType)
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
log.info("查询课程环节成功,courseId={}, lessonType={}, id={}", courseId, lessonType, result.getId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建课程环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public CourseLesson create(Long courseId, String lessonType, String name, String description,
|
||||||
|
Integer duration, String videoPath, String videoName,
|
||||||
|
String pptPath, String pptName, String pdfPath, String pdfName,
|
||||||
|
String objectives, String preparation, String extension,
|
||||||
|
String reflection, String assessmentData, Boolean useTemplate) {
|
||||||
|
log.info("创建课程环节,courseId={}, lessonType={}, name={}", courseId, lessonType, name);
|
||||||
|
// 检查是否已存在相同类型的课程
|
||||||
|
CourseLesson existing = findByType(courseId, lessonType);
|
||||||
|
if (existing != null) {
|
||||||
|
log.warn("创建课程环节失败,已存在相同类型的课程环节,courseId={}, lessonType={}", courseId, lessonType);
|
||||||
|
throw new BusinessException("该课程包已存在 " + lessonType + " 类型的课程");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最大排序号
|
||||||
|
Integer maxSortOrder = courseLessonMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CourseLesson>()
|
||||||
|
.eq(CourseLesson::getCourseId, courseId)
|
||||||
|
).stream()
|
||||||
|
.map(CourseLesson::getSortOrder)
|
||||||
|
.max(Integer::compareTo)
|
||||||
|
.orElse(0);
|
||||||
|
|
||||||
|
CourseLesson lesson = new CourseLesson();
|
||||||
|
lesson.setCourseId(courseId);
|
||||||
|
lesson.setLessonType(lessonType);
|
||||||
|
lesson.setName(name);
|
||||||
|
lesson.setDescription(description);
|
||||||
|
lesson.setDuration(duration);
|
||||||
|
lesson.setVideoPath(videoPath);
|
||||||
|
lesson.setVideoName(videoName);
|
||||||
|
lesson.setPptPath(pptPath);
|
||||||
|
lesson.setPptName(pptName);
|
||||||
|
lesson.setPdfPath(pdfPath);
|
||||||
|
lesson.setPdfName(pdfName);
|
||||||
|
lesson.setObjectives(objectives);
|
||||||
|
lesson.setPreparation(preparation);
|
||||||
|
lesson.setExtension(extension);
|
||||||
|
lesson.setReflection(reflection);
|
||||||
|
// 确保 assessment_data 为有效 JSON,MySQL JSON 列不接受纯文本
|
||||||
|
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
|
||||||
|
lesson.setUseTemplate(useTemplate);
|
||||||
|
lesson.setSortOrder(maxSortOrder + 1);
|
||||||
|
lesson.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
courseLessonMapper.insert(lesson);
|
||||||
|
log.info("课程环节创建成功,id={}, courseId={}, lessonType={}", lesson.getId(), courseId, lessonType);
|
||||||
|
return lesson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新课程环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public CourseLesson update(Long id, String name, String description, Integer duration,
|
||||||
|
String videoPath, String videoName, String pptPath, String pptName,
|
||||||
|
String pdfPath, String pdfName, String objectives, String preparation,
|
||||||
|
String extension, String reflection, String assessmentData, Boolean useTemplate) {
|
||||||
|
log.info("更新课程环节,id={}", id);
|
||||||
|
CourseLesson lesson = courseLessonMapper.selectById(id);
|
||||||
|
if (lesson == null) {
|
||||||
|
log.warn("课程环节不存在,id={}", id);
|
||||||
|
throw new BusinessException("课程不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
lesson.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
lesson.setDescription(description);
|
||||||
|
}
|
||||||
|
if (duration != null) {
|
||||||
|
lesson.setDuration(duration);
|
||||||
|
}
|
||||||
|
if (videoPath != null) {
|
||||||
|
lesson.setVideoPath(videoPath);
|
||||||
|
}
|
||||||
|
if (videoName != null) {
|
||||||
|
lesson.setVideoName(videoName);
|
||||||
|
}
|
||||||
|
if (pptPath != null) {
|
||||||
|
lesson.setPptPath(pptPath);
|
||||||
|
}
|
||||||
|
if (pptName != null) {
|
||||||
|
lesson.setPptName(pptName);
|
||||||
|
}
|
||||||
|
if (pdfPath != null) {
|
||||||
|
lesson.setPdfPath(pdfPath);
|
||||||
|
}
|
||||||
|
if (pdfName != null) {
|
||||||
|
lesson.setPdfName(pdfName);
|
||||||
|
}
|
||||||
|
if (objectives != null) {
|
||||||
|
lesson.setObjectives(objectives);
|
||||||
|
}
|
||||||
|
if (preparation != null) {
|
||||||
|
lesson.setPreparation(preparation);
|
||||||
|
}
|
||||||
|
if (extension != null) {
|
||||||
|
lesson.setExtension(extension);
|
||||||
|
}
|
||||||
|
if (reflection != null) {
|
||||||
|
lesson.setReflection(reflection);
|
||||||
|
}
|
||||||
|
if (assessmentData != null) {
|
||||||
|
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
|
||||||
|
}
|
||||||
|
if (useTemplate != null) {
|
||||||
|
lesson.setUseTemplate(useTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
lesson.setUpdatedAt(LocalDateTime.now());
|
||||||
|
courseLessonMapper.updateById(lesson);
|
||||||
|
log.info("课程环节更新成功,id={}", id);
|
||||||
|
return lesson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除课程环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void delete(Long id) {
|
||||||
|
log.info("删除课程环节,id={}", id);
|
||||||
|
CourseLesson lesson = courseLessonMapper.selectById(id);
|
||||||
|
if (lesson == null) {
|
||||||
|
log.warn("课程环节不存在,id={}", id);
|
||||||
|
throw new BusinessException("课程不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
courseLessonMapper.deleteById(id);
|
||||||
|
log.info("课程环节删除成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新排序课程环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reorder(Long courseId, List<Long> lessonIds) {
|
||||||
|
log.info("重新排序课程环节,courseId={}, count={}", courseId, lessonIds.size());
|
||||||
|
for (int i = 0; i < lessonIds.size(); i++) {
|
||||||
|
CourseLesson lesson = courseLessonMapper.selectById(lessonIds.get(i));
|
||||||
|
if (lesson != null && lesson.getCourseId().equals(courseId)) {
|
||||||
|
lesson.setSortOrder(i + 1);
|
||||||
|
courseLessonMapper.updateById(lesson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("课程环节重新排序成功,courseId={}", courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询课程环节的教学环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<LessonStep> findSteps(Long lessonId) {
|
||||||
|
log.info("查询课程环节的教学环节,lessonId={}", lessonId);
|
||||||
|
List<LessonStep> result = lessonStepMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<LessonStep>()
|
||||||
|
.eq(LessonStep::getLessonId, lessonId)
|
||||||
|
.orderByAsc(LessonStep::getSortOrder)
|
||||||
|
);
|
||||||
|
log.info("查询教学环节成功,lessonId={}, count={}", lessonId, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建教学环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public LessonStep createStep(Long lessonId, String name, String content, Integer duration,
|
||||||
|
String objective, List<Long> resourceIds) {
|
||||||
|
log.info("创建教学环节,lessonId={}, name={}", lessonId, name);
|
||||||
|
// 获取最大排序号
|
||||||
|
Integer maxSortOrder = lessonStepMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<LessonStep>()
|
||||||
|
.eq(LessonStep::getLessonId, lessonId)
|
||||||
|
).stream()
|
||||||
|
.map(LessonStep::getSortOrder)
|
||||||
|
.max(Integer::compareTo)
|
||||||
|
.orElse(0);
|
||||||
|
|
||||||
|
LessonStep step = new LessonStep();
|
||||||
|
step.setLessonId(lessonId);
|
||||||
|
step.setName(name);
|
||||||
|
step.setContent(content);
|
||||||
|
step.setDuration(duration != null ? duration : 5);
|
||||||
|
step.setObjective(objective);
|
||||||
|
step.setResourceIds(resourceIds != null ? resourceIds.toString() : null);
|
||||||
|
step.setSortOrder(maxSortOrder + 1);
|
||||||
|
step.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
lessonStepMapper.insert(step);
|
||||||
|
|
||||||
|
// 创建环节资源关联
|
||||||
|
if (resourceIds != null && !resourceIds.isEmpty()) {
|
||||||
|
for (int i = 0; i < resourceIds.size(); i++) {
|
||||||
|
LessonStepResource lsr = new LessonStepResource();
|
||||||
|
lsr.setStepId(step.getId());
|
||||||
|
lsr.setResourceId(resourceIds.get(i));
|
||||||
|
lsr.setSortOrder(i);
|
||||||
|
lessonStepResourceMapper.insert(lsr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("教学环节创建成功,id={}, lessonId={}", step.getId(), lessonId);
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新教学环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public LessonStep updateStep(Long stepId, String name, String content, Integer duration,
|
||||||
|
String objective, List<Long> resourceIds) {
|
||||||
|
log.info("更新教学环节,stepId={}", stepId);
|
||||||
|
LessonStep step = lessonStepMapper.selectById(stepId);
|
||||||
|
if (step == null) {
|
||||||
|
log.warn("教学环节不存在,stepId={}", stepId);
|
||||||
|
throw new BusinessException("教学环节不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
step.setName(name);
|
||||||
|
}
|
||||||
|
if (content != null) {
|
||||||
|
step.setContent(content);
|
||||||
|
}
|
||||||
|
if (duration != null) {
|
||||||
|
step.setDuration(duration);
|
||||||
|
}
|
||||||
|
if (objective != null) {
|
||||||
|
step.setObjective(objective);
|
||||||
|
}
|
||||||
|
if (resourceIds != null) {
|
||||||
|
step.setResourceIds(resourceIds.toString());
|
||||||
|
|
||||||
|
// 删除旧的资源关联
|
||||||
|
lessonStepResourceMapper.delete(
|
||||||
|
new LambdaQueryWrapper<LessonStepResource>()
|
||||||
|
.eq(LessonStepResource::getStepId, stepId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建新的资源关联
|
||||||
|
for (int i = 0; i < resourceIds.size(); i++) {
|
||||||
|
LessonStepResource lsr = new LessonStepResource();
|
||||||
|
lsr.setStepId(stepId);
|
||||||
|
lsr.setResourceId(resourceIds.get(i));
|
||||||
|
lsr.setSortOrder(i);
|
||||||
|
lessonStepResourceMapper.insert(lsr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step.setUpdatedAt(LocalDateTime.now());
|
||||||
|
lessonStepMapper.updateById(step);
|
||||||
|
log.info("教学环节更新成功,stepId={}", stepId);
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除教学环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteStep(Long stepId) {
|
||||||
|
log.info("删除教学环节,stepId={}", stepId);
|
||||||
|
lessonStepMapper.deleteById(stepId);
|
||||||
|
log.info("教学环节删除成功,stepId={}", stepId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新排序教学环节
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reorderSteps(Long lessonId, List<Long> stepIds) {
|
||||||
|
log.info("重新排序教学环节,lessonId={}, count={}", lessonId, stepIds.size());
|
||||||
|
for (int i = 0; i < stepIds.size(); i++) {
|
||||||
|
LessonStep step = lessonStepMapper.selectById(stepIds.get(i));
|
||||||
|
if (step != null && step.getLessonId().equals(lessonId)) {
|
||||||
|
step.setSortOrder(i + 1);
|
||||||
|
lessonStepMapper.updateById(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("教学环节重新排序成功,lessonId={}", lessonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 assessmentData 转为 MySQL JSON 列可接受的有效 JSON。
|
||||||
|
* 空值返回 null;已是有效 JSON 则原样返回;否则包装为 JSON 字符串。
|
||||||
|
*/
|
||||||
|
private String toValidJsonOrNull(String assessmentData) {
|
||||||
|
if (assessmentData == null || assessmentData.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = assessmentData.trim();
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 已是有效 JSON(对象或数组)则直接使用
|
||||||
|
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
||||||
|
try {
|
||||||
|
JSON.parse(trimmed);
|
||||||
|
return trimmed;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 解析失败,当作普通文本处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 普通文本包装为 JSON 字符串
|
||||||
|
return JSON.toJSONString(assessmentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询教师的课程环节(带权限检查)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId) {
|
||||||
|
log.info("查询教师的课程环节,courseId={}, tenantId={}", courseId, tenantId);
|
||||||
|
// 检查租户是否有权限访问该课程
|
||||||
|
TenantCourse tenantCourse = tenantCourseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<TenantCourse>()
|
||||||
|
.eq(TenantCourse::getTenantId, tenantId)
|
||||||
|
.eq(TenantCourse::getCourseId, courseId)
|
||||||
|
.eq(TenantCourse::getEnabled, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tenantCourse == null) {
|
||||||
|
// 检查是否通过套餐授权
|
||||||
|
Long count = tenantPackageMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
|
.eq(TenantPackage::getTenantId, tenantId)
|
||||||
|
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
log.warn("无权访问该课程,courseId={}, tenantId={}", courseId, tenantId);
|
||||||
|
throw new BusinessException("无权访问该课程");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CourseLesson> result = findByCourseId(courseId);
|
||||||
|
log.info("查询教师的课程环节成功,courseId={}, count={}", courseId, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
package com.reading.platform.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.reading.platform.common.exception.BusinessException;
|
||||||
|
import com.reading.platform.entity.ResourceItem;
|
||||||
|
import com.reading.platform.entity.ResourceLibrary;
|
||||||
|
import com.reading.platform.mapper.ResourceItemMapper;
|
||||||
|
import com.reading.platform.mapper.ResourceLibraryMapper;
|
||||||
|
import com.reading.platform.service.ResourceLibraryService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源库服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ResourceLibraryServiceImpl extends ServiceImpl<ResourceLibraryMapper, ResourceLibrary> implements ResourceLibraryService {
|
||||||
|
|
||||||
|
private final ResourceLibraryMapper libraryMapper;
|
||||||
|
private final ResourceItemMapper itemMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询资源库
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize) {
|
||||||
|
log.info("查询资源库列表,libraryType={}, keyword={}, page={}, pageSize={}", libraryType, keyword, page, pageSize);
|
||||||
|
Page<ResourceLibrary> pageParam = new Page<>(page, pageSize);
|
||||||
|
LambdaQueryWrapper<ResourceLibrary> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
if (libraryType != null && !libraryType.isEmpty()) {
|
||||||
|
wrapper.eq(ResourceLibrary::getLibraryType, libraryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword != null && !keyword.isEmpty()) {
|
||||||
|
wrapper.like(ResourceLibrary::getName, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.orderByDesc(ResourceLibrary::getCreatedAt);
|
||||||
|
|
||||||
|
Page<ResourceLibrary> result = libraryMapper.selectPage(pageParam, wrapper);
|
||||||
|
log.info("查询资源库列表成功,总数={}", result.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询资源库详情
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceLibrary findLibraryById(String id) {
|
||||||
|
log.info("查询资源库详情,id={}", id);
|
||||||
|
ResourceLibrary library = libraryMapper.selectById(id);
|
||||||
|
if (library == null) {
|
||||||
|
log.warn("资源库不存在,id={}", id);
|
||||||
|
throw new BusinessException("资源库不存在");
|
||||||
|
}
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询资源库(不存在时返回 null)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceLibrary findLibraryByIdOrNull(String id) {
|
||||||
|
if (id == null || id.isEmpty()) return null;
|
||||||
|
return libraryMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建资源库
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceLibrary createLibrary(String name, String type, String description, String tenantId) {
|
||||||
|
log.info("创建资源库,name={}, type={}, tenantId={}", name, type, tenantId);
|
||||||
|
ResourceLibrary library = new ResourceLibrary();
|
||||||
|
library.setName(name);
|
||||||
|
library.setLibraryType(type);
|
||||||
|
library.setDescription(description);
|
||||||
|
library.setTenantId(tenantId);
|
||||||
|
library.setStatus("ACTIVE");
|
||||||
|
library.setSortOrder(0);
|
||||||
|
|
||||||
|
libraryMapper.insert(library);
|
||||||
|
log.info("资源库创建成功,id={}", library.getId());
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新资源库
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceLibrary updateLibrary(String id, String name, String description) {
|
||||||
|
log.info("更新资源库,id={}, name={}, description={}", id, name, description);
|
||||||
|
ResourceLibrary library = libraryMapper.selectById(id);
|
||||||
|
if (library == null) {
|
||||||
|
log.warn("资源库不存在,id={}", id);
|
||||||
|
throw new BusinessException("资源库不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
library.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
library.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryMapper.updateById(library);
|
||||||
|
log.info("资源库更新成功,id={}", id);
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除资源库
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void deleteLibrary(String id) {
|
||||||
|
log.info("删除资源库,id={}", id);
|
||||||
|
// 先删除该资源库下的所有资源项
|
||||||
|
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(ResourceItem::getLibraryId, id);
|
||||||
|
itemMapper.delete(wrapper);
|
||||||
|
|
||||||
|
libraryMapper.deleteById(id);
|
||||||
|
log.info("资源库删除成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询资源项目
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize) {
|
||||||
|
log.info("查询资源项目列表,libraryId={}, fileType={}, keyword={}, page={}, pageSize={}", libraryId, fileType, keyword, page, pageSize);
|
||||||
|
Page<ResourceItem> pageParam = new Page<>(page, pageSize);
|
||||||
|
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
if (libraryId != null && !libraryId.isEmpty()) {
|
||||||
|
wrapper.eq(ResourceItem::getLibraryId, libraryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType != null && !fileType.isEmpty()) {
|
||||||
|
wrapper.eq(ResourceItem::getFileType, fileType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword != null && !keyword.isEmpty()) {
|
||||||
|
wrapper.and(w -> w.like(ResourceItem::getTitle, keyword)
|
||||||
|
.or()
|
||||||
|
.like(ResourceItem::getDescription, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.orderByDesc(ResourceItem::getCreatedAt);
|
||||||
|
|
||||||
|
Page<ResourceItem> result = itemMapper.selectPage(pageParam, wrapper);
|
||||||
|
log.info("查询资源项目列表成功,总数={}", result.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询资源项目详情
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceItem findItemById(String id) {
|
||||||
|
log.info("查询资源项目详情,id={}", id);
|
||||||
|
ResourceItem item = itemMapper.selectById(id);
|
||||||
|
if (item == null) {
|
||||||
|
log.warn("资源项目不存在,id={}", id);
|
||||||
|
throw new BusinessException("资源项目不存在");
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建资源项目(数字资源)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceItem createItem(String libraryId, String title, String fileType, String filePath,
|
||||||
|
Long fileSize, String description, String tags, String tenantId) {
|
||||||
|
log.info("创建资源项目,libraryId={}, title={}, fileType={}, fileSize={}, tenantId={}", libraryId, title, fileType, fileSize, tenantId);
|
||||||
|
ResourceItem item = new ResourceItem();
|
||||||
|
item.setId(null); // 确保由数据库 AUTO_INCREMENT 生成,避免主键冲突
|
||||||
|
item.setLibraryId(libraryId); // 目标资源库 ID
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setName(title); // 数据库 name 字段 NOT NULL,与 title 保持一致
|
||||||
|
item.setDescription(description);
|
||||||
|
item.setFileType(fileType);
|
||||||
|
item.setFilePath(filePath);
|
||||||
|
item.setFileSize(fileSize);
|
||||||
|
item.setTags(tags);
|
||||||
|
item.setSortOrder(0);
|
||||||
|
item.setTenantId(tenantId);
|
||||||
|
item.setStatus("ACTIVE");
|
||||||
|
|
||||||
|
itemMapper.insert(item);
|
||||||
|
log.info("资源项目创建成功,id={}", item.getId());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新资源项目(数字资源)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResourceItem updateItem(String id, String title, String description, String tags) {
|
||||||
|
log.info("更新资源项目,id={}, title={}, description={}, tags={}", id, title, description, tags);
|
||||||
|
ResourceItem item = itemMapper.selectById(id);
|
||||||
|
if (item == null) {
|
||||||
|
log.warn("资源项目不存在,id={}", id);
|
||||||
|
throw new BusinessException("资源项目不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title != null) {
|
||||||
|
item.setTitle(title);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
item.setDescription(description);
|
||||||
|
}
|
||||||
|
if (tags != null) {
|
||||||
|
item.setTags(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemMapper.updateById(item);
|
||||||
|
log.info("资源项目更新成功,id={}", id);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除资源项目
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void deleteItem(String id) {
|
||||||
|
log.info("删除资源项目,id={}", id);
|
||||||
|
itemMapper.deleteById(id);
|
||||||
|
log.info("资源项目删除成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除资源项目
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void batchDeleteItems(List<String> ids) {
|
||||||
|
log.info("批量删除资源项目,ids={}", ids);
|
||||||
|
itemMapper.deleteBatchIds(ids);
|
||||||
|
log.info("批量删除资源项目成功,数量={}", ids.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计数据
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getStats() {
|
||||||
|
log.info("获取资源库统计数据");
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
|
||||||
|
Long libraryCount = libraryMapper.selectCount(null);
|
||||||
|
Long itemCount = itemMapper.selectCount(null);
|
||||||
|
|
||||||
|
stats.put("totalLibraries", libraryCount);
|
||||||
|
stats.put("totalItems", itemCount);
|
||||||
|
|
||||||
|
// 按资源库类型统计资源数量(绘本资源、教学材料等)
|
||||||
|
Map<String, Long> itemsByLibraryType = new HashMap<>();
|
||||||
|
LambdaQueryWrapper<ResourceLibrary> libWrapper = new LambdaQueryWrapper<>();
|
||||||
|
for (ResourceLibrary lib : libraryMapper.selectList(libWrapper)) {
|
||||||
|
String type = lib.getLibraryType();
|
||||||
|
if (type != null) {
|
||||||
|
LambdaQueryWrapper<ResourceItem> itemWrapper = new LambdaQueryWrapper<>();
|
||||||
|
itemWrapper.eq(ResourceItem::getLibraryId, String.valueOf(lib.getId()));
|
||||||
|
long count = itemMapper.selectCount(itemWrapper);
|
||||||
|
itemsByLibraryType.merge(type, count, Long::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.put("itemsByLibraryType", itemsByLibraryType);
|
||||||
|
|
||||||
|
log.info("资源库统计数据:totalLibraries={}, totalItems={}, itemsByLibraryType={}", libraryCount, itemCount, itemsByLibraryType);
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
package com.reading.platform.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.reading.platform.common.exception.BusinessException;
|
||||||
|
import com.reading.platform.entity.CoursePackage;
|
||||||
|
import com.reading.platform.entity.Theme;
|
||||||
|
import com.reading.platform.mapper.CoursePackageMapper;
|
||||||
|
import com.reading.platform.mapper.ThemeMapper;
|
||||||
|
import com.reading.platform.service.ThemeService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题字典服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ThemeServiceImpl extends ServiceImpl<ThemeMapper, Theme> implements ThemeService {
|
||||||
|
|
||||||
|
private final ThemeMapper themeMapper;
|
||||||
|
private final CoursePackageMapper courseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有主题
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Theme> findAll() {
|
||||||
|
log.info("查询所有主题");
|
||||||
|
List<Theme> result = lambdaQuery()
|
||||||
|
.orderByAsc(Theme::getSortOrder)
|
||||||
|
.list();
|
||||||
|
log.info("查询所有主题成功,count={}", result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询主题详情
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Theme findById(Long id) {
|
||||||
|
log.info("查询主题详情,id={}", id);
|
||||||
|
Theme theme = themeMapper.selectById(id);
|
||||||
|
if (theme == null) {
|
||||||
|
log.warn("主题不存在,id={}", id);
|
||||||
|
throw new BusinessException("主题不存在");
|
||||||
|
}
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建主题
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Theme create(String name, String description, Integer sortOrder) {
|
||||||
|
log.info("创建主题,name={}, sortOrder={}", name, sortOrder);
|
||||||
|
// 获取最大排序号
|
||||||
|
Integer maxSortOrder = themeMapper.selectList(null)
|
||||||
|
.stream()
|
||||||
|
.map(Theme::getSortOrder)
|
||||||
|
.max(Integer::compareTo)
|
||||||
|
.orElse(0);
|
||||||
|
|
||||||
|
Theme theme = new Theme();
|
||||||
|
theme.setName(name);
|
||||||
|
theme.setDescription(description);
|
||||||
|
theme.setSortOrder(sortOrder != null ? sortOrder : maxSortOrder + 1);
|
||||||
|
theme.setStatus("ACTIVE");
|
||||||
|
themeMapper.insert(theme);
|
||||||
|
|
||||||
|
log.info("主题创建成功,id={}", theme.getId());
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新主题
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Theme update(Long id, String name, String description, Integer sortOrder, String status) {
|
||||||
|
log.info("更新主题,id={}, name={}, status={}", id, name, status);
|
||||||
|
Theme theme = themeMapper.selectById(id);
|
||||||
|
if (theme == null) {
|
||||||
|
log.warn("主题不存在,id={}", id);
|
||||||
|
throw new BusinessException("主题不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
theme.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
theme.setDescription(description);
|
||||||
|
}
|
||||||
|
if (sortOrder != null) {
|
||||||
|
theme.setSortOrder(sortOrder);
|
||||||
|
}
|
||||||
|
if (status != null) {
|
||||||
|
theme.setStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMapper.updateById(theme);
|
||||||
|
log.info("主题更新成功,id={}", id);
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除主题
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void delete(Long id) {
|
||||||
|
log.info("删除主题,id={}", id);
|
||||||
|
// 检查是否有关联课程
|
||||||
|
Long courseCount = courseMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<CoursePackage>().eq(CoursePackage::getThemeId, id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (courseCount > 0) {
|
||||||
|
log.warn("删除主题失败,该主题下有 {} 个课程包,id={}", courseCount, id);
|
||||||
|
throw new BusinessException("该主题下有 " + courseCount + " 个课程包,无法删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMapper.deleteById(id);
|
||||||
|
log.info("主题删除成功,id={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新排序
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reorder(List<Long> ids) {
|
||||||
|
log.info("重新排序主题,count={}", ids.size());
|
||||||
|
for (int i = 0; i < ids.size(); i++) {
|
||||||
|
Theme theme = themeMapper.selectById(ids.get(i));
|
||||||
|
if (theme != null) {
|
||||||
|
theme.setSortOrder(i + 1);
|
||||||
|
themeMapper.updateById(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("主题重新排序成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user