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;
|
||||
|
||||
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.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.reading.platform.dto.response.CourseCollectionResponse;
|
||||
import com.reading.platform.dto.response.CoursePackageResponse;
|
||||
import com.reading.platform.entity.CoursePackage;
|
||||
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.util.ArrayList;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 课程套餐服务(两层结构-最上层)
|
||||
* 课程套餐服务接口(两层结构 - 最上层)
|
||||
*/
|
||||
@Slf4j
|
||||
@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 interface CourseCollectionService extends IService<CourseCollection> {
|
||||
|
||||
/**
|
||||
* 查询租户的课程套餐列表
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<CourseCollectionResponse> findTenantCollections(Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取课程套餐详情(包含课程包列表)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
CourseCollectionResponse getCollectionDetail(Long collectionId);
|
||||
|
||||
/**
|
||||
* 分页查询课程套餐
|
||||
*/
|
||||
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;
|
||||
}
|
||||
Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status);
|
||||
|
||||
/**
|
||||
* 获取课程套餐下的课程包列表
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<CoursePackageResponse> getPackagesByCollection(Long collectionId);
|
||||
|
||||
/**
|
||||
* 创建课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
CourseCollectionResponse createCollection(String name, String description, Long price,
|
||||
Long discountPrice, String discountType, String[] gradeLevels);
|
||||
|
||||
/**
|
||||
* 设置课程套餐的课程包
|
||||
*/
|
||||
@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("课程套餐的课程包设置完成");
|
||||
}
|
||||
void setCollectionPackages(Long collectionId, List<Long> packageIds);
|
||||
|
||||
/**
|
||||
* 更新课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
CourseCollectionResponse updateCollection(Long id, String name, String description, Long price,
|
||||
Long discountPrice, String discountType, String[] gradeLevels);
|
||||
|
||||
/**
|
||||
* 删除课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void deleteCollection(Long id);
|
||||
|
||||
/**
|
||||
* 发布课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void publishCollection(Long id);
|
||||
|
||||
/**
|
||||
* 下架课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void archiveCollection(Long id);
|
||||
|
||||
/**
|
||||
* 重新发布课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void republishCollection(Long id);
|
||||
|
||||
/**
|
||||
* 撤销审核
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void withdrawCollection(Long id);
|
||||
|
||||
/**
|
||||
* 提交课程套餐审核
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void submitCollection(Long id);
|
||||
|
||||
/**
|
||||
* 审核驳回课程套餐
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void rejectCollection(Long id, String comment);
|
||||
|
||||
/**
|
||||
* 续费租户课程套餐
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
void renewTenantCollection(Long tenantId, Long collectionId, LocalDate endDate, Long pricePaid);
|
||||
}
|
||||
|
||||
@ -1,417 +1,87 @@
|
||||
package com.reading.platform.service;
|
||||
|
||||
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.mapper.TenantCourseMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.reading.platform.entity.CourseLesson;
|
||||
import com.reading.platform.entity.LessonStep;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 课程环节服务
|
||||
* 课程环节服务接口
|
||||
*/
|
||||
@Slf4j
|
||||
@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 interface CourseLessonService extends IService<CourseLesson> {
|
||||
|
||||
/**
|
||||
* 查询课程的所有环节
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<CourseLesson> findByCourseId(Long courseId);
|
||||
|
||||
/**
|
||||
* 查询课程环节详情
|
||||
*/
|
||||
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;
|
||||
}
|
||||
CourseLesson findById(Long id);
|
||||
|
||||
/**
|
||||
* 按类型查询课程环节
|
||||
*/
|
||||
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;
|
||||
}
|
||||
CourseLesson findByType(Long courseId, String lessonType);
|
||||
|
||||
/**
|
||||
* 创建课程环节
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
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);
|
||||
|
||||
/**
|
||||
* 更新课程环节
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
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);
|
||||
|
||||
/**
|
||||
* 删除课程环节
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void delete(Long id);
|
||||
|
||||
/**
|
||||
* 重新排序课程环节
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void reorder(Long courseId, List<Long> lessonIds);
|
||||
|
||||
/**
|
||||
* 查询课程环节的教学环节
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<LessonStep> findSteps(Long lessonId);
|
||||
|
||||
/**
|
||||
* 创建教学环节
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
LessonStep createStep(Long lessonId, String name, String content, Integer duration,
|
||||
String objective, List<Long> resourceIds);
|
||||
|
||||
/**
|
||||
* 更新教学环节
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
LessonStep updateStep(Long stepId, String name, String content, Integer duration,
|
||||
String objective, List<Long> resourceIds);
|
||||
|
||||
/**
|
||||
* 删除教学环节
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStep(Long stepId) {
|
||||
log.info("删除教学环节,stepId={}", stepId);
|
||||
lessonStepMapper.deleteById(stepId);
|
||||
log.info("教学环节删除成功,stepId={}", stepId);
|
||||
}
|
||||
void deleteStep(Long stepId);
|
||||
|
||||
/**
|
||||
* 重新排序教学环节
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void reorderSteps(Long lessonId, List<Long> stepIds);
|
||||
|
||||
/**
|
||||
* 查询教师的课程环节(带权限检查)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId);
|
||||
}
|
||||
|
||||
@ -1,271 +1,81 @@
|
||||
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.service.impl.ServiceImpl;
|
||||
import com.reading.platform.common.exception.BusinessException;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
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 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 ResourceLibraryService extends ServiceImpl<ResourceLibraryMapper, ResourceLibrary> {
|
||||
|
||||
private final ResourceLibraryMapper libraryMapper;
|
||||
private final ResourceItemMapper itemMapper;
|
||||
public interface ResourceLibraryService extends IService<ResourceLibrary> {
|
||||
|
||||
/**
|
||||
* 分页查询资源库
|
||||
*/
|
||||
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;
|
||||
}
|
||||
Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 查询资源库详情
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceLibrary findLibraryById(String id);
|
||||
|
||||
/**
|
||||
* 查询资源库(不存在时返回 null)
|
||||
*/
|
||||
public ResourceLibrary findLibraryByIdOrNull(String id) {
|
||||
if (id == null || id.isEmpty()) return null;
|
||||
return libraryMapper.selectById(id);
|
||||
}
|
||||
ResourceLibrary findLibraryByIdOrNull(String id);
|
||||
|
||||
/**
|
||||
* 创建资源库
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceLibrary createLibrary(String name, String type, String description, String tenantId);
|
||||
|
||||
/**
|
||||
* 更新资源库
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceLibrary updateLibrary(String id, String name, String description);
|
||||
|
||||
/**
|
||||
* 删除资源库
|
||||
*/
|
||||
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);
|
||||
}
|
||||
void deleteLibrary(String id);
|
||||
|
||||
/**
|
||||
* 分页查询资源项目
|
||||
*/
|
||||
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;
|
||||
}
|
||||
Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 查询资源项目详情
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceItem findItemById(String id);
|
||||
|
||||
/**
|
||||
* 创建资源项目(数字资源)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceItem createItem(String libraryId, String title, String fileType, String filePath,
|
||||
Long fileSize, String description, String tags, String tenantId);
|
||||
|
||||
/**
|
||||
* 更新资源项目(数字资源)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
ResourceItem updateItem(String id, String title, String description, String tags);
|
||||
|
||||
/**
|
||||
* 删除资源项目
|
||||
*/
|
||||
public void deleteItem(String id) {
|
||||
log.info("删除资源项目,id={}", id);
|
||||
itemMapper.deleteById(id);
|
||||
log.info("资源项目删除成功,id={}", id);
|
||||
}
|
||||
void deleteItem(String id);
|
||||
|
||||
/**
|
||||
* 批量删除资源项目
|
||||
*/
|
||||
public void batchDeleteItems(List<String> ids) {
|
||||
log.info("批量删除资源项目,ids={}", ids);
|
||||
itemMapper.deleteBatchIds(ids);
|
||||
log.info("批量删除资源项目成功,数量={}", ids.size());
|
||||
}
|
||||
void batchDeleteItems(List<String> ids);
|
||||
|
||||
/**
|
||||
* 获取统计数据
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
Map<String, Object> getStats();
|
||||
}
|
||||
|
||||
@ -1,142 +1,42 @@
|
||||
package com.reading.platform.service;
|
||||
|
||||
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.baomidou.mybatisplus.extension.service.IService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 主题字典服务
|
||||
* 主题字典服务接口
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ThemeService extends ServiceImpl<ThemeMapper, Theme> {
|
||||
|
||||
private final ThemeMapper themeMapper;
|
||||
private final CoursePackageMapper courseMapper;
|
||||
public interface ThemeService extends IService<Theme> {
|
||||
|
||||
/**
|
||||
* 查询所有主题
|
||||
*/
|
||||
public List<Theme> findAll() {
|
||||
log.info("查询所有主题");
|
||||
List<Theme> result = lambdaQuery()
|
||||
.orderByAsc(Theme::getSortOrder)
|
||||
.list();
|
||||
log.info("查询所有主题成功,count={}", result.size());
|
||||
return result;
|
||||
}
|
||||
List<Theme> findAll();
|
||||
|
||||
/**
|
||||
* 查询主题详情
|
||||
*/
|
||||
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;
|
||||
}
|
||||
Theme findById(Long id);
|
||||
|
||||
/**
|
||||
* 创建主题
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
Theme create(String name, String description, Integer sortOrder);
|
||||
|
||||
/**
|
||||
* 更新主题
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
Theme update(Long id, String name, String description, Integer sortOrder, String status);
|
||||
|
||||
/**
|
||||
* 删除主题
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
void delete(Long id);
|
||||
|
||||
/**
|
||||
* 重新排序
|
||||
*/
|
||||
@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("主题重新排序成功");
|
||||
}
|
||||
void reorder(List<Long> ids);
|
||||
}
|
||||
|
||||
@ -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