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:
En 2026-03-19 15:08:01 +08:00
parent efedb37cae
commit 67b87fae73
9 changed files with 1637 additions and 1226 deletions

View 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`,保持当前结构即可

View File

@ -1,625 +1,93 @@
package com.reading.platform.service; package com.reading.platform.service;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.IService;
import com.reading.platform.common.enums.CourseStatus;
import com.reading.platform.common.enums.TenantPackageStatus;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.dto.response.CourseCollectionResponse; import com.reading.platform.dto.response.CourseCollectionResponse;
import com.reading.platform.dto.response.CoursePackageResponse; import com.reading.platform.dto.response.CoursePackageResponse;
import com.reading.platform.entity.CoursePackage;
import com.reading.platform.entity.CourseCollection; import com.reading.platform.entity.CourseCollection;
import com.reading.platform.entity.CourseCollectionPackage;
import com.reading.platform.entity.CoursePackageCourse;
import com.reading.platform.entity.CourseLesson;
import com.reading.platform.entity.TenantPackage;
import com.reading.platform.mapper.CourseCollectionMapper;
import com.reading.platform.mapper.CourseCollectionPackageMapper;
import com.reading.platform.mapper.CoursePackageMapper;
import com.reading.platform.mapper.CoursePackageCourseMapper;
import com.reading.platform.mapper.TenantPackageMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 课程套餐服务两层结构-最上层 * 课程套餐服务接口两层结构 - 最上层
*/ */
@Slf4j public interface CourseCollectionService extends IService<CourseCollection> {
@Service
@RequiredArgsConstructor
public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper, CourseCollection> {
private final CourseCollectionMapper collectionMapper;
private final CourseCollectionPackageMapper collectionPackageMapper;
private final CoursePackageMapper packageMapper;
private final TenantPackageMapper tenantPackageMapper;
private final CoursePackageCourseMapper packageCoursePackageMapper;
private final CoursePackageMapper courseMapper;
private final CourseLessonService courseLessonService;
/** /**
* 查询租户的课程套餐列表 * 查询租户的课程套餐列表
*/ */
public List<CourseCollectionResponse> findTenantCollections(Long tenantId) { List<CourseCollectionResponse> findTenantCollections(Long tenantId);
log.info("查询租户课程套餐tenantId={}", tenantId);
// 查询租户的课程套餐关联
List<TenantPackage> tenantPackages = tenantPackageMapper.selectList(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getTenantId, tenantId)
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE.getCode())
.isNotNull(TenantPackage::getCollectionId)
.orderByDesc(TenantPackage::getCreatedAt)
);
log.info("查询到{}条租户套餐关联记录", tenantPackages.size());
for (TenantPackage tp : tenantPackages) {
log.info(" - collection_id: {}, start_date: {}, end_date: {}",
tp.getCollectionId(), tp.getStartDate(), tp.getEndDate());
}
// 获取套餐详情并转换为响应对象
List<CourseCollectionResponse> result = tenantPackages.stream()
.map(tp -> {
CourseCollection collection = collectionMapper.selectById(tp.getCollectionId());
if (collection == null) {
log.warn("套餐不存在collection_id={}", tp.getCollectionId());
return null;
}
// 过滤下架状态的课程套餐
if (CourseStatus.ARCHIVED.getCode().equals(collection.getStatus())) {
log.warn("套餐已下架collection_id={}", tp.getCollectionId());
return null;
}
log.info("返回套餐: id={}, name={}, package_count={}",
collection.getId(), collection.getName(), collection.getPackageCount());
CourseCollectionResponse response = toResponse(collection);
// 设置租户套餐的额外信息如果有
response.setStartDate(tp.getStartDate());
response.setEndDate(tp.getEndDate());
return response;
})
.filter(r -> r != null)
.collect(Collectors.toList());
log.info("查询到{}个课程套餐", result.size());
return result;
}
/** /**
* 获取课程套餐详情包含课程包列表 * 获取课程套餐详情包含课程包列表
*/ */
public CourseCollectionResponse getCollectionDetail(Long collectionId) { CourseCollectionResponse getCollectionDetail(Long collectionId);
log.info("获取课程套餐详情collectionId={}", collectionId);
CourseCollection collection = collectionMapper.selectById(collectionId);
if (collection == null) {
log.warn("课程套餐不存在collectionId={}", collectionId);
return null;
}
return toResponse(collection);
}
/** /**
* 分页查询课程套餐 * 分页查询课程套餐
*/ */
public Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status) { Page<CourseCollectionResponse> pageCollections(Integer pageNum, Integer pageSize, String status);
log.info("分页查询课程套餐pageNum={}, pageSize={}, status={}", pageNum, pageSize, status);
LambdaQueryWrapper<CourseCollection> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(status)) {
wrapper.eq(CourseCollection::getStatus, status);
}
wrapper.orderByDesc(CourseCollection::getCreatedAt);
Page<CourseCollection> page = collectionMapper.selectPage(
new Page<>(pageNum, pageSize),
wrapper
);
// 转换为响应对象
List<CourseCollectionResponse> responses = page.getRecords().stream()
.map(this::toResponse)
.collect(Collectors.toList());
Page<CourseCollectionResponse> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
result.setRecords(responses);
return result;
}
/** /**
* 获取课程套餐下的课程包列表 * 获取课程套餐下的课程包列表
*/ */
public List<CoursePackageResponse> getPackagesByCollection(Long collectionId) { List<CoursePackageResponse> getPackagesByCollection(Long collectionId);
log.info("获取课程套餐的课程包列表collectionId={}", collectionId);
// 查询关联关系
List<CourseCollectionPackage> associations = collectionPackageMapper.selectList(
new LambdaQueryWrapper<CourseCollectionPackage>()
.eq(CourseCollectionPackage::getCollectionId, collectionId)
.orderByAsc(CourseCollectionPackage::getSortOrder)
);
if (associations.isEmpty()) {
return new ArrayList<>();
}
// 获取课程包详情
List<Long> packageIds = associations.stream()
.map(CourseCollectionPackage::getPackageId)
.collect(Collectors.toList());
List<CoursePackage> packages = packageMapper.selectList(
new LambdaQueryWrapper<CoursePackage>()
.in(CoursePackage::getId, packageIds)
.eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode())
);
// 转换为响应对象并设置排序号
List<CoursePackageResponse> result = packages.stream()
.map(pkg -> {
CoursePackageResponse response = toPackageResponse(pkg);
// 设置排序号
associations.stream()
.filter(a -> a.getPackageId().equals(pkg.getId()))
.findFirst()
.ifPresent(a -> response.setSortOrder(a.getSortOrder()));
return response;
})
.collect(Collectors.toList());
log.info("查询到{}个课程包", result.size());
return result;
}
/** /**
* 创建课程套餐 * 创建课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) CourseCollectionResponse createCollection(String name, String description, Long price,
public CourseCollectionResponse createCollection(String name, String description, Long price, Long discountPrice, String discountType, String[] gradeLevels);
Long discountPrice, String discountType, String[] gradeLevels) {
log.info("创建课程套餐name={}", name);
CourseCollection collection = new CourseCollection();
collection.setName(name);
collection.setDescription(description);
collection.setPrice(price);
collection.setDiscountPrice(discountPrice);
collection.setDiscountType(discountType);
// 将数组转为 JSON 字符串存储到 JSON 字段
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
collection.setPackageCount(0);
collection.setStatus(CourseStatus.DRAFT.getCode());
collectionMapper.insert(collection);
log.info("课程套餐创建成功id={}", collection.getId());
return toResponse(collection);
}
/** /**
* 设置课程套餐的课程包 * 设置课程套餐的课程包
*/ */
@Transactional(rollbackFor = Exception.class) void setCollectionPackages(Long collectionId, List<Long> packageIds);
public void setCollectionPackages(Long collectionId, List<Long> packageIds) {
log.info("设置课程套餐的课程包collectionId={}, packageCount={}", collectionId, packageIds.size());
// 验证课程套餐是否存在
CourseCollection collection = collectionMapper.selectById(collectionId);
if (collection == null) {
throw new BusinessException("课程套餐不存在");
}
// 去重保持首次出现顺序避免 uk_collection_package 唯一约束冲突
List<Long> distinctIds = packageIds.stream().distinct().collect(Collectors.toList());
// 验证课程包是否存在应用层外键约束
if (!distinctIds.isEmpty()) {
List<CoursePackage> packages = packageMapper.selectList(
new LambdaQueryWrapper<CoursePackage>()
.in(CoursePackage::getId, distinctIds)
);
if (packages.size() != distinctIds.size()) {
throw new BusinessException("存在无效的课程包 ID");
}
}
// 删除旧的关联
collectionPackageMapper.delete(
new LambdaQueryWrapper<CourseCollectionPackage>()
.eq(CourseCollectionPackage::getCollectionId, collectionId)
);
// 创建新的关联
for (int i = 0; i < distinctIds.size(); i++) {
CourseCollectionPackage association = new CourseCollectionPackage();
association.setCollectionId(collectionId);
association.setPackageId(distinctIds.get(i));
association.setSortOrder(i + 1);
collectionPackageMapper.insert(association);
}
// 更新课程包数量复用前面已验证的 collection 变量
collection.setPackageCount(distinctIds.size());
collectionMapper.updateById(collection);
log.info("课程套餐的课程包设置完成");
}
/** /**
* 更新课程套餐 * 更新课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) CourseCollectionResponse updateCollection(Long id, String name, String description, Long price,
public CourseCollectionResponse updateCollection(Long id, String name, String description, Long price, Long discountPrice, String discountType, String[] gradeLevels);
Long discountPrice, String discountType, String[] gradeLevels) {
log.info("更新课程套餐id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setName(name);
collection.setDescription(description);
collection.setPrice(price);
collection.setDiscountPrice(discountPrice);
collection.setDiscountType(discountType);
// 将数组转为 JSON 字符串存储到 JSON 字段
collection.setGradeLevels(JSON.toJSONString(gradeLevels));
collectionMapper.updateById(collection);
log.info("课程套餐更新成功id={}", id);
return toResponse(collection);
}
/** /**
* 删除课程套餐 * 删除课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void deleteCollection(Long id);
public void deleteCollection(Long id) {
log.info("删除课程套餐id={}", id);
// 检查是否有租户正在使用此课程套餐
Long tenantCount = tenantPackageMapper.selectCount(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getCollectionId, id)
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
);
if (tenantCount > 0) {
log.warn("删除课程套餐失败,有 {} 个租户正在使用此套餐id={}", tenantCount, id);
throw new BusinessException("" + tenantCount + " 个租户正在使用此课程套餐,无法删除");
}
// 清理所有状态的租户套餐关联记录包括非活跃状态
List<TenantPackage> allTenantPackages = tenantPackageMapper.selectList(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getCollectionId, id)
);
if (!allTenantPackages.isEmpty()) {
tenantPackageMapper.delete(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getCollectionId, id)
);
log.info("已清理 {} 条租户套餐关联记录", allTenantPackages.size());
}
// 删除课程套餐与课程包的关联关系
List<CourseCollectionPackage> collectionPackages = collectionPackageMapper.selectList(
new LambdaQueryWrapper<CourseCollectionPackage>()
.eq(CourseCollectionPackage::getCollectionId, id)
);
if (!collectionPackages.isEmpty()) {
collectionPackageMapper.delete(
new LambdaQueryWrapper<CourseCollectionPackage>()
.eq(CourseCollectionPackage::getCollectionId, id)
);
log.info("已清理 {} 条课程包关联记录", collectionPackages.size());
}
// 删除课程套餐
collectionMapper.deleteById(id);
log.info("课程套餐删除成功id={}", id);
}
/** /**
* 发布课程套餐 * 发布课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void publishCollection(Long id);
public void publishCollection(Long id) {
log.info("发布课程套餐id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setStatus(CourseStatus.PUBLISHED.getCode());
collection.setPublishedAt(LocalDateTime.now());
collectionMapper.updateById(collection);
log.info("课程套餐发布成功id={}", id);
}
/** /**
* 下架课程套餐 * 下架课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void archiveCollection(Long id);
public void archiveCollection(Long id) {
log.info("下架课程套餐id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setStatus(CourseStatus.ARCHIVED.getCode());
collectionMapper.updateById(collection);
log.info("课程套餐下架成功id={}", id);
}
/** /**
* 重新发布课程套餐 * 重新发布课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void republishCollection(Long id);
public void republishCollection(Long id) {
log.info("重新发布课程套餐id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setStatus(CourseStatus.PUBLISHED.getCode());
collection.setPublishedAt(LocalDateTime.now());
collectionMapper.updateById(collection);
log.info("课程套餐重新发布成功id={}", id);
}
/** /**
* 撤销审核 * 撤销审核
*/ */
@Transactional(rollbackFor = Exception.class) void withdrawCollection(Long id);
public void withdrawCollection(Long id) {
log.info("撤销课程套餐审核id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setStatus(CourseStatus.DRAFT.getCode());
collectionMapper.updateById(collection);
log.info("课程套餐审核撤销成功id={}", id);
}
/** /**
* 提交课程套餐审核 * 提交课程套餐审核
*/ */
@Transactional(rollbackFor = Exception.class) void submitCollection(Long id);
public void submitCollection(Long id) {
log.info("提交课程套餐审核id={}", id);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
// 检查是否包含课程包
if (collection.getPackageCount() == null || collection.getPackageCount() == 0) {
throw new BusinessException("课程套餐必须包含至少一个课程包");
}
collection.setStatus(CourseStatus.PENDING.getCode());
collection.setSubmittedAt(LocalDateTime.now());
collectionMapper.updateById(collection);
log.info("课程套餐提交审核成功id={}", id);
}
/** /**
* 审核驳回课程套餐 * 审核驳回课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void rejectCollection(Long id, String comment);
public void rejectCollection(Long id, String comment) {
log.info("审核驳回课程套餐id={}, comment={}", id, comment);
CourseCollection collection = collectionMapper.selectById(id);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
collection.setStatus(CourseStatus.REJECTED.getCode());
collection.setReviewedAt(LocalDateTime.now());
collection.setReviewComment(comment);
collectionMapper.updateById(collection);
log.info("课程套餐审核驳回成功id={}", id);
}
/** /**
* 续费租户课程套餐 * 续费租户课程套餐
*/ */
@Transactional(rollbackFor = Exception.class) void renewTenantCollection(Long tenantId, Long collectionId, LocalDate endDate, Long pricePaid);
public void renewTenantCollection(Long tenantId, Long collectionId, java.time.LocalDate endDate, Long pricePaid) {
log.info("续费租户课程套餐tenantId={}, collectionId={}, endDate={}, pricePaid={}", tenantId, collectionId, endDate, pricePaid);
// 查询现有租户套餐关联
List<TenantPackage> existingPackages = tenantPackageMapper.selectList(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getTenantId, tenantId)
.eq(TenantPackage::getCollectionId, collectionId)
);
if (!existingPackages.isEmpty()) {
// 更新现有记录
TenantPackage existing = existingPackages.get(0);
existing.setEndDate(endDate);
existing.setStatus(TenantPackageStatus.ACTIVE);
if (pricePaid != null) {
existing.setPricePaid(pricePaid);
}
existing.setUpdatedAt(LocalDateTime.now());
tenantPackageMapper.updateById(existing);
log.info("租户课程套餐续费成功tenantId={}, collectionId={}", tenantId, collectionId);
} else {
// 查询课程套餐信息
CourseCollection collection = collectionMapper.selectById(collectionId);
if (collection == null) {
throw new IllegalArgumentException("课程套餐不存在");
}
// 创建新记录
TenantPackage tp = new TenantPackage();
tp.setTenantId(tenantId);
tp.setCollectionId(collectionId);
tp.setStartDate(java.time.LocalDate.now());
tp.setEndDate(endDate);
tp.setStatus(TenantPackageStatus.ACTIVE);
tp.setPricePaid(pricePaid != null ? pricePaid :
(collection.getDiscountPrice() != null ? collection.getDiscountPrice() : collection.getPrice()));
tp.setCreatedAt(LocalDateTime.now());
tenantPackageMapper.insert(tp);
log.info("租户课程套餐新办成功tenantId={}, collectionId={}", tenantId, collectionId);
}
}
/**
* 解析适用年级数据库存储为 JSON 数组 ["小班","中班","大班"]需正确解析
*/
private String[] parseGradeLevels(String gradeLevels) {
if (!StringUtils.hasText(gradeLevels)) {
return new String[0];
}
try {
if (gradeLevels.trim().startsWith("[")) {
return JSON.parseArray(gradeLevels, String.class).toArray(new String[0]);
}
// 兼容旧数据逗号分隔格式
return gradeLevels.split(",");
} catch (Exception e) {
log.warn("解析 gradeLevels 失败: {}", gradeLevels, e);
return new String[0];
}
}
/**
* 转换为响应对象
*/
private CourseCollectionResponse toResponse(CourseCollection collection) {
// 获取课程包列表
List<CourseCollectionResponse.CoursePackageItem> packages = getPackagesByCollection(collection.getId()).stream()
.map(pkg -> {
CourseCollectionResponse.CoursePackageItem item = new CourseCollectionResponse.CoursePackageItem();
item.setId(pkg.getId());
item.setName(pkg.getName());
item.setDescription(pkg.getDescription());
item.setGradeLevels(pkg.getGradeLevels() != null ? pkg.getGradeLevels() : new String[0]);
item.setCourseCount(pkg.getCourseCount() != null ? pkg.getCourseCount() : 0);
item.setSortOrder(pkg.getSortOrder());
return item;
})
.collect(Collectors.toList());
return CourseCollectionResponse.builder()
.id(collection.getId())
.name(collection.getName())
.description(collection.getDescription())
.price(collection.getPrice())
.discountPrice(collection.getDiscountPrice())
.discountType(collection.getDiscountType())
.gradeLevels(parseGradeLevels(collection.getGradeLevels()))
.packageCount(collection.getPackageCount())
.status(collection.getStatus())
.submittedAt(collection.getSubmittedAt())
.submittedBy(collection.getSubmittedBy())
.reviewedAt(collection.getReviewedAt())
.reviewedBy(collection.getReviewedBy())
.reviewComment(collection.getReviewComment())
.publishedAt(collection.getPublishedAt())
.createdAt(collection.getCreatedAt())
.updatedAt(collection.getUpdatedAt())
.packages(packages)
.build();
}
/**
* 转换为课程包响应对象包含课程列表和排课计划参考
*/
private CoursePackageResponse toPackageResponse(CoursePackage pkg) {
// 解析 gradeTagsCoursePackage 使用 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();
}
} }

View File

@ -1,417 +1,87 @@
package com.reading.platform.service; package com.reading.platform.service;
import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.reading.platform.entity.CourseLesson;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.reading.platform.entity.LessonStep;
import com.reading.platform.common.enums.TenantPackageStatus;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.entity.*;
import com.reading.platform.mapper.*;
import com.reading.platform.mapper.TenantCourseMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
* 课程环节服务 * 课程环节服务接口
*/ */
@Slf4j public interface CourseLessonService extends IService<CourseLesson> {
@Service
@RequiredArgsConstructor
public class CourseLessonService extends ServiceImpl<CourseLessonMapper, CourseLesson> {
private final CourseLessonMapper courseLessonMapper;
private final LessonStepMapper lessonStepMapper;
private final LessonStepResourceMapper lessonStepResourceMapper;
private final TenantCourseMapper tenantCourseMapper;
private final TenantPackageMapper tenantPackageMapper;
/** /**
* 查询课程的所有环节 * 查询课程的所有环节
*/ */
public List<CourseLesson> findByCourseId(Long courseId) { List<CourseLesson> findByCourseId(Long courseId);
log.info("查询课程的所有环节courseId={}", courseId);
List<CourseLesson> result = courseLessonMapper.selectList(
new LambdaQueryWrapper<CourseLesson>()
.eq(CourseLesson::getCourseId, courseId)
.orderByAsc(CourseLesson::getSortOrder)
);
log.info("查询课程环节成功courseId={}, count={}", courseId, result.size());
return result;
}
/** /**
* 查询课程环节详情 * 查询课程环节详情
*/ */
public CourseLesson findById(Long id) { CourseLesson findById(Long id);
log.info("查询课程环节详情id={}", id);
CourseLesson lesson = courseLessonMapper.selectById(id);
if (lesson == null) {
log.warn("课程环节不存在id={}", id);
throw new BusinessException("课程环节不存在");
}
return lesson;
}
/** /**
* 按类型查询课程环节 * 按类型查询课程环节
*/ */
public CourseLesson findByType(Long courseId, String lessonType) { CourseLesson findByType(Long courseId, String lessonType);
log.info("按类型查询课程环节courseId={}, lessonType={}", courseId, lessonType);
CourseLesson result = courseLessonMapper.selectOne(
new LambdaQueryWrapper<CourseLesson>()
.eq(CourseLesson::getCourseId, courseId)
.eq(CourseLesson::getLessonType, lessonType)
);
if (result != null) {
log.info("查询课程环节成功courseId={}, lessonType={}, id={}", courseId, lessonType, result.getId());
}
return result;
}
/** /**
* 创建课程环节 * 创建课程环节
*/ */
@Transactional(rollbackFor = Exception.class) CourseLesson create(Long courseId, String lessonType, String name, String description,
public CourseLesson create(Long courseId, String lessonType, String name, String description, Integer duration, String videoPath, String videoName,
Integer duration, String videoPath, String videoName, String pptPath, String pptName, String pdfPath, String pdfName,
String pptPath, String pptName, String pdfPath, String pdfName, String objectives, String preparation, String extension,
String objectives, String preparation, String extension, String reflection, String assessmentData, Boolean useTemplate);
String reflection, String assessmentData, Boolean useTemplate) {
log.info("创建课程环节courseId={}, lessonType={}, name={}", courseId, lessonType, name);
// 检查是否已存在相同类型的课程
CourseLesson existing = findByType(courseId, lessonType);
if (existing != null) {
log.warn("创建课程环节失败已存在相同类型的课程环节courseId={}, lessonType={}", courseId, lessonType);
throw new BusinessException("该课程包已存在 " + lessonType + " 类型的课程");
}
// 获取最大排序号
Integer maxSortOrder = courseLessonMapper.selectList(
new LambdaQueryWrapper<CourseLesson>()
.eq(CourseLesson::getCourseId, courseId)
).stream()
.map(CourseLesson::getSortOrder)
.max(Integer::compareTo)
.orElse(0);
CourseLesson lesson = new CourseLesson();
lesson.setCourseId(courseId);
lesson.setLessonType(lessonType);
lesson.setName(name);
lesson.setDescription(description);
lesson.setDuration(duration);
lesson.setVideoPath(videoPath);
lesson.setVideoName(videoName);
lesson.setPptPath(pptPath);
lesson.setPptName(pptName);
lesson.setPdfPath(pdfPath);
lesson.setPdfName(pdfName);
lesson.setObjectives(objectives);
lesson.setPreparation(preparation);
lesson.setExtension(extension);
lesson.setReflection(reflection);
// 确保 assessment_data 为有效 JSONMySQL JSON 列不接受纯文本
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
lesson.setUseTemplate(useTemplate);
lesson.setSortOrder(maxSortOrder + 1);
lesson.setCreatedAt(LocalDateTime.now());
courseLessonMapper.insert(lesson);
log.info("课程环节创建成功id={}, courseId={}, lessonType={}", lesson.getId(), courseId, lessonType);
return lesson;
}
/** /**
* 更新课程环节 * 更新课程环节
*/ */
@Transactional(rollbackFor = Exception.class) CourseLesson update(Long id, String name, String description, Integer duration,
public CourseLesson update(Long id, String name, String description, Integer duration, String videoPath, String videoName, String pptPath, String pptName,
String videoPath, String videoName, String pptPath, String pptName, String pdfPath, String pdfName, String objectives, String preparation,
String pdfPath, String pdfName, String objectives, String preparation, String extension, String reflection, String assessmentData, Boolean useTemplate);
String extension, String reflection, String assessmentData, Boolean useTemplate) {
log.info("更新课程环节id={}", id);
CourseLesson lesson = courseLessonMapper.selectById(id);
if (lesson == null) {
log.warn("课程环节不存在id={}", id);
throw new BusinessException("课程不存在");
}
if (name != null) {
lesson.setName(name);
}
if (description != null) {
lesson.setDescription(description);
}
if (duration != null) {
lesson.setDuration(duration);
}
if (videoPath != null) {
lesson.setVideoPath(videoPath);
}
if (videoName != null) {
lesson.setVideoName(videoName);
}
if (pptPath != null) {
lesson.setPptPath(pptPath);
}
if (pptName != null) {
lesson.setPptName(pptName);
}
if (pdfPath != null) {
lesson.setPdfPath(pdfPath);
}
if (pdfName != null) {
lesson.setPdfName(pdfName);
}
if (objectives != null) {
lesson.setObjectives(objectives);
}
if (preparation != null) {
lesson.setPreparation(preparation);
}
if (extension != null) {
lesson.setExtension(extension);
}
if (reflection != null) {
lesson.setReflection(reflection);
}
if (assessmentData != null) {
lesson.setAssessmentData(toValidJsonOrNull(assessmentData));
}
if (useTemplate != null) {
lesson.setUseTemplate(useTemplate);
}
lesson.setUpdatedAt(LocalDateTime.now());
courseLessonMapper.updateById(lesson);
log.info("课程环节更新成功id={}", id);
return lesson;
}
/** /**
* 删除课程环节 * 删除课程环节
*/ */
@Transactional(rollbackFor = Exception.class) void delete(Long id);
public void delete(Long id) {
log.info("删除课程环节id={}", id);
CourseLesson lesson = courseLessonMapper.selectById(id);
if (lesson == null) {
log.warn("课程环节不存在id={}", id);
throw new BusinessException("课程不存在");
}
courseLessonMapper.deleteById(id);
log.info("课程环节删除成功id={}", id);
}
/** /**
* 重新排序课程环节 * 重新排序课程环节
*/ */
@Transactional(rollbackFor = Exception.class) void reorder(Long courseId, List<Long> lessonIds);
public void reorder(Long courseId, List<Long> lessonIds) {
log.info("重新排序课程环节courseId={}, count={}", courseId, lessonIds.size());
for (int i = 0; i < lessonIds.size(); i++) {
CourseLesson lesson = courseLessonMapper.selectById(lessonIds.get(i));
if (lesson != null && lesson.getCourseId().equals(courseId)) {
lesson.setSortOrder(i + 1);
courseLessonMapper.updateById(lesson);
}
}
log.info("课程环节重新排序成功courseId={}", courseId);
}
/** /**
* 查询课程环节的教学环节 * 查询课程环节的教学环节
*/ */
public List<LessonStep> findSteps(Long lessonId) { List<LessonStep> findSteps(Long lessonId);
log.info("查询课程环节的教学环节lessonId={}", lessonId);
List<LessonStep> result = lessonStepMapper.selectList(
new LambdaQueryWrapper<LessonStep>()
.eq(LessonStep::getLessonId, lessonId)
.orderByAsc(LessonStep::getSortOrder)
);
log.info("查询教学环节成功lessonId={}, count={}", lessonId, result.size());
return result;
}
/** /**
* 创建教学环节 * 创建教学环节
*/ */
@Transactional(rollbackFor = Exception.class) LessonStep createStep(Long lessonId, String name, String content, Integer duration,
public LessonStep createStep(Long lessonId, String name, String content, Integer duration, String objective, List<Long> resourceIds);
String objective, List<Long> resourceIds) {
log.info("创建教学环节lessonId={}, name={}", lessonId, name);
// 获取最大排序号
Integer maxSortOrder = lessonStepMapper.selectList(
new LambdaQueryWrapper<LessonStep>()
.eq(LessonStep::getLessonId, lessonId)
).stream()
.map(LessonStep::getSortOrder)
.max(Integer::compareTo)
.orElse(0);
LessonStep step = new LessonStep();
step.setLessonId(lessonId);
step.setName(name);
step.setContent(content);
step.setDuration(duration != null ? duration : 5);
step.setObjective(objective);
step.setResourceIds(resourceIds != null ? resourceIds.toString() : null);
step.setSortOrder(maxSortOrder + 1);
step.setCreatedAt(LocalDateTime.now());
lessonStepMapper.insert(step);
// 创建环节资源关联
if (resourceIds != null && !resourceIds.isEmpty()) {
for (int i = 0; i < resourceIds.size(); i++) {
LessonStepResource lsr = new LessonStepResource();
lsr.setStepId(step.getId());
lsr.setResourceId(resourceIds.get(i));
lsr.setSortOrder(i);
lessonStepResourceMapper.insert(lsr);
}
}
log.info("教学环节创建成功id={}, lessonId={}", step.getId(), lessonId);
return step;
}
/** /**
* 更新教学环节 * 更新教学环节
*/ */
@Transactional(rollbackFor = Exception.class) LessonStep updateStep(Long stepId, String name, String content, Integer duration,
public LessonStep updateStep(Long stepId, String name, String content, Integer duration, String objective, List<Long> resourceIds);
String objective, List<Long> resourceIds) {
log.info("更新教学环节stepId={}", stepId);
LessonStep step = lessonStepMapper.selectById(stepId);
if (step == null) {
log.warn("教学环节不存在stepId={}", stepId);
throw new BusinessException("教学环节不存在");
}
if (name != null) {
step.setName(name);
}
if (content != null) {
step.setContent(content);
}
if (duration != null) {
step.setDuration(duration);
}
if (objective != null) {
step.setObjective(objective);
}
if (resourceIds != null) {
step.setResourceIds(resourceIds.toString());
// 删除旧的资源关联
lessonStepResourceMapper.delete(
new LambdaQueryWrapper<LessonStepResource>()
.eq(LessonStepResource::getStepId, stepId)
);
// 创建新的资源关联
for (int i = 0; i < resourceIds.size(); i++) {
LessonStepResource lsr = new LessonStepResource();
lsr.setStepId(stepId);
lsr.setResourceId(resourceIds.get(i));
lsr.setSortOrder(i);
lessonStepResourceMapper.insert(lsr);
}
}
step.setUpdatedAt(LocalDateTime.now());
lessonStepMapper.updateById(step);
log.info("教学环节更新成功stepId={}", stepId);
return step;
}
/** /**
* 删除教学环节 * 删除教学环节
*/ */
@Transactional(rollbackFor = Exception.class) void deleteStep(Long stepId);
public void deleteStep(Long stepId) {
log.info("删除教学环节stepId={}", stepId);
lessonStepMapper.deleteById(stepId);
log.info("教学环节删除成功stepId={}", stepId);
}
/** /**
* 重新排序教学环节 * 重新排序教学环节
*/ */
@Transactional(rollbackFor = Exception.class) void reorderSteps(Long lessonId, List<Long> stepIds);
public void reorderSteps(Long lessonId, List<Long> stepIds) {
log.info("重新排序教学环节lessonId={}, count={}", lessonId, stepIds.size());
for (int i = 0; i < stepIds.size(); i++) {
LessonStep step = lessonStepMapper.selectById(stepIds.get(i));
if (step != null && step.getLessonId().equals(lessonId)) {
step.setSortOrder(i + 1);
lessonStepMapper.updateById(step);
}
}
log.info("教学环节重新排序成功lessonId={}", lessonId);
}
/**
* assessmentData 转为 MySQL JSON 列可接受的有效 JSON
* 空值返回 null已是有效 JSON 则原样返回否则包装为 JSON 字符串
*/
private String toValidJsonOrNull(String assessmentData) {
if (assessmentData == null || assessmentData.isEmpty()) {
return null;
}
String trimmed = assessmentData.trim();
if (trimmed.isEmpty()) {
return null;
}
// 已是有效 JSON对象或数组则直接使用
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
try {
JSON.parse(trimmed);
return trimmed;
} catch (Exception ignored) {
// 解析失败当作普通文本处理
}
}
// 普通文本包装为 JSON 字符串
return JSON.toJSONString(assessmentData);
}
/** /**
* 查询教师的课程环节带权限检查 * 查询教师的课程环节带权限检查
*/ */
public List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId) { List<CourseLesson> findCourseLessonsForTeacher(Long courseId, Long tenantId);
log.info("查询教师的课程环节courseId={}, tenantId={}", courseId, tenantId);
// 检查租户是否有权限访问该课程
TenantCourse tenantCourse = tenantCourseMapper.selectOne(
new LambdaQueryWrapper<TenantCourse>()
.eq(TenantCourse::getTenantId, tenantId)
.eq(TenantCourse::getCourseId, courseId)
.eq(TenantCourse::getEnabled, 1)
);
if (tenantCourse == null) {
// 检查是否通过套餐授权
Long count = tenantPackageMapper.selectCount(
new LambdaQueryWrapper<TenantPackage>()
.eq(TenantPackage::getTenantId, tenantId)
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
);
if (count == 0) {
log.warn("无权访问该课程courseId={}, tenantId={}", courseId, tenantId);
throw new BusinessException("无权访问该课程");
}
}
List<CourseLesson> result = findByCourseId(courseId);
log.info("查询教师的课程环节成功courseId={}, count={}", courseId, result.size());
return result;
}
} }

View File

@ -1,271 +1,81 @@
package com.reading.platform.service; package com.reading.platform.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.IService;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.entity.ResourceItem; import com.reading.platform.entity.ResourceItem;
import com.reading.platform.entity.ResourceLibrary; import com.reading.platform.entity.ResourceLibrary;
import com.reading.platform.mapper.ResourceItemMapper;
import com.reading.platform.mapper.ResourceLibraryMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* 资源库服务 * 资源库服务接口
*/ */
@Slf4j public interface ResourceLibraryService extends IService<ResourceLibrary> {
@Service
@RequiredArgsConstructor
public class ResourceLibraryService extends ServiceImpl<ResourceLibraryMapper, ResourceLibrary> {
private final ResourceLibraryMapper libraryMapper;
private final ResourceItemMapper itemMapper;
/** /**
* 分页查询资源库 * 分页查询资源库
*/ */
public Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize) { Page<ResourceLibrary> findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize);
log.info("查询资源库列表libraryType={}, keyword={}, page={}, pageSize={}", libraryType, keyword, page, pageSize);
Page<ResourceLibrary> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<ResourceLibrary> wrapper = new LambdaQueryWrapper<>();
if (libraryType != null && !libraryType.isEmpty()) {
wrapper.eq(ResourceLibrary::getLibraryType, libraryType);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.like(ResourceLibrary::getName, keyword);
}
wrapper.orderByDesc(ResourceLibrary::getCreatedAt);
Page<ResourceLibrary> result = libraryMapper.selectPage(pageParam, wrapper);
log.info("查询资源库列表成功,总数={}", result.getTotal());
return result;
}
/** /**
* 查询资源库详情 * 查询资源库详情
*/ */
public ResourceLibrary findLibraryById(String id) { ResourceLibrary findLibraryById(String id);
log.info("查询资源库详情id={}", id);
ResourceLibrary library = libraryMapper.selectById(id);
if (library == null) {
log.warn("资源库不存在id={}", id);
throw new BusinessException("资源库不存在");
}
return library;
}
/** /**
* 查询资源库不存在时返回 null * 查询资源库不存在时返回 null
*/ */
public ResourceLibrary findLibraryByIdOrNull(String id) { ResourceLibrary findLibraryByIdOrNull(String id);
if (id == null || id.isEmpty()) return null;
return libraryMapper.selectById(id);
}
/** /**
* 创建资源库 * 创建资源库
*/ */
public ResourceLibrary createLibrary(String name, String type, String description, String tenantId) { ResourceLibrary createLibrary(String name, String type, String description, String tenantId);
log.info("创建资源库name={}, type={}, tenantId={}", name, type, tenantId);
ResourceLibrary library = new ResourceLibrary();
library.setName(name);
library.setLibraryType(type);
library.setDescription(description);
library.setTenantId(tenantId);
library.setStatus("ACTIVE");
library.setSortOrder(0);
libraryMapper.insert(library);
log.info("资源库创建成功id={}", library.getId());
return library;
}
/** /**
* 更新资源库 * 更新资源库
*/ */
public ResourceLibrary updateLibrary(String id, String name, String description) { ResourceLibrary updateLibrary(String id, String name, String description);
log.info("更新资源库id={}, name={}, description={}", id, name, description);
ResourceLibrary library = libraryMapper.selectById(id);
if (library == null) {
log.warn("资源库不存在id={}", id);
throw new BusinessException("资源库不存在");
}
if (name != null) {
library.setName(name);
}
if (description != null) {
library.setDescription(description);
}
libraryMapper.updateById(library);
log.info("资源库更新成功id={}", id);
return library;
}
/** /**
* 删除资源库 * 删除资源库
*/ */
public void deleteLibrary(String id) { void deleteLibrary(String id);
log.info("删除资源库id={}", id);
// 先删除该资源库下的所有资源项
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ResourceItem::getLibraryId, id);
itemMapper.delete(wrapper);
libraryMapper.deleteById(id);
log.info("资源库删除成功id={}", id);
}
/** /**
* 分页查询资源项目 * 分页查询资源项目
*/ */
public Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize) { Page<ResourceItem> findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize);
log.info("查询资源项目列表libraryId={}, fileType={}, keyword={}, page={}, pageSize={}", libraryId, fileType, keyword, page, pageSize);
Page<ResourceItem> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<ResourceItem> wrapper = new LambdaQueryWrapper<>();
if (libraryId != null && !libraryId.isEmpty()) {
wrapper.eq(ResourceItem::getLibraryId, libraryId);
}
if (fileType != null && !fileType.isEmpty()) {
wrapper.eq(ResourceItem::getFileType, fileType);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(ResourceItem::getTitle, keyword)
.or()
.like(ResourceItem::getDescription, keyword));
}
wrapper.orderByDesc(ResourceItem::getCreatedAt);
Page<ResourceItem> result = itemMapper.selectPage(pageParam, wrapper);
log.info("查询资源项目列表成功,总数={}", result.getTotal());
return result;
}
/** /**
* 查询资源项目详情 * 查询资源项目详情
*/ */
public ResourceItem findItemById(String id) { ResourceItem findItemById(String id);
log.info("查询资源项目详情id={}", id);
ResourceItem item = itemMapper.selectById(id);
if (item == null) {
log.warn("资源项目不存在id={}", id);
throw new BusinessException("资源项目不存在");
}
return item;
}
/** /**
* 创建资源项目数字资源 * 创建资源项目数字资源
*/ */
public ResourceItem createItem(String libraryId, String title, String fileType, String filePath, ResourceItem createItem(String libraryId, String title, String fileType, String filePath,
Long fileSize, String description, String tags, String tenantId) { Long fileSize, String description, String tags, String tenantId);
log.info("创建资源项目libraryId={}, title={}, fileType={}, fileSize={}, tenantId={}", libraryId, title, fileType, fileSize, tenantId);
ResourceItem item = new ResourceItem();
item.setId(null); // 确保由数据库 AUTO_INCREMENT 生成避免主键冲突
item.setLibraryId(libraryId); // 目标资源库 ID
item.setTitle(title);
item.setName(title); // 数据库 name 字段 NOT NULL title 保持一致
item.setDescription(description);
item.setFileType(fileType);
item.setFilePath(filePath);
item.setFileSize(fileSize);
item.setTags(tags);
item.setSortOrder(0);
item.setTenantId(tenantId);
item.setStatus("ACTIVE");
itemMapper.insert(item);
log.info("资源项目创建成功id={}", item.getId());
return item;
}
/** /**
* 更新资源项目数字资源 * 更新资源项目数字资源
*/ */
public ResourceItem updateItem(String id, String title, String description, String tags) { ResourceItem updateItem(String id, String title, String description, String tags);
log.info("更新资源项目id={}, title={}, description={}, tags={}", id, title, description, tags);
ResourceItem item = itemMapper.selectById(id);
if (item == null) {
log.warn("资源项目不存在id={}", id);
throw new BusinessException("资源项目不存在");
}
if (title != null) {
item.setTitle(title);
}
if (description != null) {
item.setDescription(description);
}
if (tags != null) {
item.setTags(tags);
}
itemMapper.updateById(item);
log.info("资源项目更新成功id={}", id);
return item;
}
/** /**
* 删除资源项目 * 删除资源项目
*/ */
public void deleteItem(String id) { void deleteItem(String id);
log.info("删除资源项目id={}", id);
itemMapper.deleteById(id);
log.info("资源项目删除成功id={}", id);
}
/** /**
* 批量删除资源项目 * 批量删除资源项目
*/ */
public void batchDeleteItems(List<String> ids) { void batchDeleteItems(List<String> ids);
log.info("批量删除资源项目ids={}", ids);
itemMapper.deleteBatchIds(ids);
log.info("批量删除资源项目成功,数量={}", ids.size());
}
/** /**
* 获取统计数据 * 获取统计数据
*/ */
public Map<String, Object> getStats() { Map<String, Object> getStats();
log.info("获取资源库统计数据");
Map<String, Object> stats = new HashMap<>();
Long libraryCount = libraryMapper.selectCount(null);
Long itemCount = itemMapper.selectCount(null);
stats.put("totalLibraries", libraryCount);
stats.put("totalItems", itemCount);
// 按资源库类型统计资源数量绘本资源教学材料等
Map<String, Long> itemsByLibraryType = new HashMap<>();
LambdaQueryWrapper<ResourceLibrary> libWrapper = new LambdaQueryWrapper<>();
for (ResourceLibrary lib : libraryMapper.selectList(libWrapper)) {
String type = lib.getLibraryType();
if (type != null) {
LambdaQueryWrapper<ResourceItem> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.eq(ResourceItem::getLibraryId, String.valueOf(lib.getId()));
long count = itemMapper.selectCount(itemWrapper);
itemsByLibraryType.merge(type, count, Long::sum);
}
}
stats.put("itemsByLibraryType", itemsByLibraryType);
log.info("资源库统计数据totalLibraries={}, totalItems={}, itemsByLibraryType={}", libraryCount, itemCount, itemsByLibraryType);
return stats;
}
} }

View File

@ -1,142 +1,42 @@
package com.reading.platform.service; package com.reading.platform.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.entity.CoursePackage;
import com.reading.platform.entity.Theme; import com.reading.platform.entity.Theme;
import com.reading.platform.mapper.CoursePackageMapper;
import com.reading.platform.mapper.ThemeMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
/** /**
* 主题字典服务 * 主题字典服务接口
*/ */
@Slf4j public interface ThemeService extends IService<Theme> {
@Service
@RequiredArgsConstructor
public class ThemeService extends ServiceImpl<ThemeMapper, Theme> {
private final ThemeMapper themeMapper;
private final CoursePackageMapper courseMapper;
/** /**
* 查询所有主题 * 查询所有主题
*/ */
public List<Theme> findAll() { List<Theme> findAll();
log.info("查询所有主题");
List<Theme> result = lambdaQuery()
.orderByAsc(Theme::getSortOrder)
.list();
log.info("查询所有主题成功count={}", result.size());
return result;
}
/** /**
* 查询主题详情 * 查询主题详情
*/ */
public Theme findById(Long id) { Theme findById(Long id);
log.info("查询主题详情id={}", id);
Theme theme = themeMapper.selectById(id);
if (theme == null) {
log.warn("主题不存在id={}", id);
throw new BusinessException("主题不存在");
}
return theme;
}
/** /**
* 创建主题 * 创建主题
*/ */
@Transactional(rollbackFor = Exception.class) Theme create(String name, String description, Integer sortOrder);
public Theme create(String name, String description, Integer sortOrder) {
log.info("创建主题name={}, sortOrder={}", name, sortOrder);
// 获取最大排序号
Integer maxSortOrder = themeMapper.selectList(null)
.stream()
.map(Theme::getSortOrder)
.max(Integer::compareTo)
.orElse(0);
Theme theme = new Theme();
theme.setName(name);
theme.setDescription(description);
theme.setSortOrder(sortOrder != null ? sortOrder : maxSortOrder + 1);
theme.setStatus("ACTIVE");
themeMapper.insert(theme);
log.info("主题创建成功id={}", theme.getId());
return theme;
}
/** /**
* 更新主题 * 更新主题
*/ */
@Transactional(rollbackFor = Exception.class) Theme update(Long id, String name, String description, Integer sortOrder, String status);
public Theme update(Long id, String name, String description, Integer sortOrder, String status) {
log.info("更新主题id={}, name={}, status={}", id, name, status);
Theme theme = themeMapper.selectById(id);
if (theme == null) {
log.warn("主题不存在id={}", id);
throw new BusinessException("主题不存在");
}
if (name != null) {
theme.setName(name);
}
if (description != null) {
theme.setDescription(description);
}
if (sortOrder != null) {
theme.setSortOrder(sortOrder);
}
if (status != null) {
theme.setStatus(status);
}
themeMapper.updateById(theme);
log.info("主题更新成功id={}", id);
return theme;
}
/** /**
* 删除主题 * 删除主题
*/ */
@Transactional(rollbackFor = Exception.class) void delete(Long id);
public void delete(Long id) {
log.info("删除主题id={}", id);
// 检查是否有关联课程
Long courseCount = courseMapper.selectCount(
new LambdaQueryWrapper<CoursePackage>().eq(CoursePackage::getThemeId, id)
);
if (courseCount > 0) {
log.warn("删除主题失败,该主题下有 {} 个课程包id={}", courseCount, id);
throw new BusinessException("该主题下有 " + courseCount + " 个课程包,无法删除");
}
themeMapper.deleteById(id);
log.info("主题删除成功id={}", id);
}
/** /**
* 重新排序 * 重新排序
*/ */
@Transactional(rollbackFor = Exception.class) void reorder(List<Long> ids);
public void reorder(List<Long> ids) {
log.info("重新排序主题count={}", ids.size());
for (int i = 0; i < ids.size(); i++) {
Theme theme = themeMapper.selectById(ids.get(i));
if (theme != null) {
theme.setSortOrder(i + 1);
themeMapper.updateById(theme);
}
}
log.info("主题重新排序成功");
}
} }

View File

@ -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) {
// 解析 gradeTagsCoursePackage 使用 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();
}
}

View File

@ -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 为有效 JSONMySQL 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;
}
}

View File

@ -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;
}
}

View File

@ -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("主题重新排序成功");
}
}