diff --git a/docs/dev-logs/2026-03-19.md b/docs/dev-logs/2026-03-19.md new file mode 100644 index 0000000..70df82e --- /dev/null +++ b/docs/dev-logs/2026-03-19.md @@ -0,0 +1,65 @@ +# 开发日志 - 2026-03-19 + +## Service 层重构 + +### 背景 + +根据项目统一开发规范要求,Service 层应采用 **接口 + 实现类** 的结构: + +``` +service/ +├── XxxService.java ← 接口,继承 IService +└── 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` 并实现 `ThemeService` 接口 + +#### 2. ResourceLibraryService +- **接口方法**:`findAllLibraries()`, `findLibraryById()`, `createLibrary()`, `updateLibrary()`, `deleteLibrary()`, `findAllItems()`, `findItemById()`, `createItem()`, `updateItem()`, `deleteItem()`, `batchDeleteItems()`, `getStats()` +- **实现类**:`ResourceLibraryServiceImpl` 继承 `ServiceImpl` 并实现 `ResourceLibraryService` 接口 + +#### 3. CourseLessonService +- **接口方法**:`findByCourseId()`, `findById()`, `findByType()`, `create()`, `update()`, `delete()`, `reorder()`, `findSteps()`, `createStep()`, `updateStep()`, `deleteStep()`, `reorderSteps()`, `findCourseLessonsForTeacher()` +- **实现类**:`CourseLessonServiceImpl` 继承 `ServiceImpl` 并实现 `CourseLessonService` 接口 + +#### 4. CourseCollectionService +- **接口方法**:`findTenantCollections()`, `getCollectionDetail()`, `pageCollections()`, `getPackagesByCollection()`, `createCollection()`, `setCollectionPackages()`, `updateCollection()`, `deleteCollection()`, `publishCollection()`, `archiveCollection()`, `republishCollection()`, `withdrawCollection()`, `submitCollection()`, `rejectCollection()`, `renewTenantCollection()` +- **实现类**:`CourseCollectionServiceImpl` 继承 `ServiceImpl` 并实现 `CourseCollectionService` 接口 +- **依赖注入**:注入 `CourseLessonService` 用于查询课程环节 + +### 验证结果 + +```bash +export JAVA_HOME="/f/Java/jdk-17" +mvn compile -DskipTests +``` + +**编译结果**:✅ BUILD SUCCESS + +### 影响范围 + +- Controller 层引用保持不变(Spring 自动注入接口实现) +- 其他服务层调用保持不变 +- 无数据库变更 +- 无 API 变更 + +### 备注 + +- `FileStorageService` 是纯工具类服务,不涉及数据库操作,无需继承 `IService`,保持当前结构即可 diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java index 07f6b78..d017e61 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java @@ -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 { - - 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 { /** * 查询租户的课程套餐列表 */ - public List findTenantCollections(Long tenantId) { - log.info("查询租户课程套餐,tenantId={}", tenantId); - - // 查询租户的课程套餐关联 - List tenantPackages = tenantPackageMapper.selectList( - new LambdaQueryWrapper() - .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 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 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 pageCollections(Integer pageNum, Integer pageSize, String status) { - log.info("分页查询课程套餐,pageNum={}, pageSize={}, status={}", pageNum, pageSize, status); - - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - if (StringUtils.hasText(status)) { - wrapper.eq(CourseCollection::getStatus, status); - } - wrapper.orderByDesc(CourseCollection::getCreatedAt); - - Page page = collectionMapper.selectPage( - new Page<>(pageNum, pageSize), - wrapper - ); - - // 转换为响应对象 - List responses = page.getRecords().stream() - .map(this::toResponse) - .collect(Collectors.toList()); - - Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); - result.setRecords(responses); - return result; - } + Page pageCollections(Integer pageNum, Integer pageSize, String status); /** * 获取课程套餐下的课程包列表 */ - public List getPackagesByCollection(Long collectionId) { - log.info("获取课程套餐的课程包列表,collectionId={}", collectionId); - - // 查询关联关系 - List associations = collectionPackageMapper.selectList( - new LambdaQueryWrapper() - .eq(CourseCollectionPackage::getCollectionId, collectionId) - .orderByAsc(CourseCollectionPackage::getSortOrder) - ); - - if (associations.isEmpty()) { - return new ArrayList<>(); - } - - // 获取课程包详情 - List packageIds = associations.stream() - .map(CourseCollectionPackage::getPackageId) - .collect(Collectors.toList()); - - List packages = packageMapper.selectList( - new LambdaQueryWrapper() - .in(CoursePackage::getId, packageIds) - .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode()) - ); - - // 转换为响应对象并设置排序号 - List 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 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 packageIds) { - log.info("设置课程套餐的课程包,collectionId={}, packageCount={}", collectionId, packageIds.size()); - - // 验证课程套餐是否存在 - CourseCollection collection = collectionMapper.selectById(collectionId); - if (collection == null) { - throw new BusinessException("课程套餐不存在"); - } - - // 去重:保持首次出现顺序,避免 uk_collection_package 唯一约束冲突 - List distinctIds = packageIds.stream().distinct().collect(Collectors.toList()); - - // 验证课程包是否存在(应用层外键约束) - if (!distinctIds.isEmpty()) { - List packages = packageMapper.selectList( - new LambdaQueryWrapper() - .in(CoursePackage::getId, distinctIds) - ); - if (packages.size() != distinctIds.size()) { - throw new BusinessException("存在无效的课程包 ID"); - } - } - - // 删除旧的关联 - collectionPackageMapper.delete( - new LambdaQueryWrapper() - .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 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() - .eq(TenantPackage::getCollectionId, id) - .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE) - ); - - if (tenantCount > 0) { - log.warn("删除课程套餐失败,有 {} 个租户正在使用此套餐,id={}", tenantCount, id); - throw new BusinessException("有 " + tenantCount + " 个租户正在使用此课程套餐,无法删除"); - } - - // 清理所有状态的租户套餐关联记录(包括非活跃状态) - List allTenantPackages = tenantPackageMapper.selectList( - new LambdaQueryWrapper() - .eq(TenantPackage::getCollectionId, id) - ); - - if (!allTenantPackages.isEmpty()) { - tenantPackageMapper.delete( - new LambdaQueryWrapper() - .eq(TenantPackage::getCollectionId, id) - ); - log.info("已清理 {} 条租户套餐关联记录", allTenantPackages.size()); - } - - // 删除课程套餐与课程包的关联关系 - List collectionPackages = collectionPackageMapper.selectList( - new LambdaQueryWrapper() - .eq(CourseCollectionPackage::getCollectionId, id) - ); - - if (!collectionPackages.isEmpty()) { - collectionPackageMapper.delete( - new LambdaQueryWrapper() - .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 existingPackages = tenantPackageMapper.selectList( - new LambdaQueryWrapper() - .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 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 courseItems = new ArrayList<>(); - - // 查询课程包关联的课程环节 - List 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); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java index 4610006..3c77094 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java @@ -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 { - - 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 { /** * 查询课程的所有环节 */ - public List findByCourseId(Long courseId) { - log.info("查询课程的所有环节,courseId={}", courseId); - List result = courseLessonMapper.selectList( - new LambdaQueryWrapper() - .eq(CourseLesson::getCourseId, courseId) - .orderByAsc(CourseLesson::getSortOrder) - ); - log.info("查询课程环节成功,courseId={}, count={}", courseId, result.size()); - return result; - } + List 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() - .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() - .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 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 lessonIds); /** * 查询课程环节的教学环节 */ - public List findSteps(Long lessonId) { - log.info("查询课程环节的教学环节,lessonId={}", lessonId); - List result = lessonStepMapper.selectList( - new LambdaQueryWrapper() - .eq(LessonStep::getLessonId, lessonId) - .orderByAsc(LessonStep::getSortOrder) - ); - log.info("查询教学环节成功,lessonId={}, count={}", lessonId, result.size()); - return result; - } + List findSteps(Long lessonId); /** * 创建教学环节 */ - @Transactional(rollbackFor = Exception.class) - public LessonStep createStep(Long lessonId, String name, String content, Integer duration, - String objective, List resourceIds) { - log.info("创建教学环节,lessonId={}, name={}", lessonId, name); - // 获取最大排序号 - Integer maxSortOrder = lessonStepMapper.selectList( - new LambdaQueryWrapper() - .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 resourceIds); /** * 更新教学环节 */ - @Transactional(rollbackFor = Exception.class) - public LessonStep updateStep(Long stepId, String name, String content, Integer duration, - String objective, List 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() - .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 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 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 stepIds); /** * 查询教师的课程环节(带权限检查) */ - public List findCourseLessonsForTeacher(Long courseId, Long tenantId) { - log.info("查询教师的课程环节,courseId={}, tenantId={}", courseId, tenantId); - // 检查租户是否有权限访问该课程 - TenantCourse tenantCourse = tenantCourseMapper.selectOne( - new LambdaQueryWrapper() - .eq(TenantCourse::getTenantId, tenantId) - .eq(TenantCourse::getCourseId, courseId) - .eq(TenantCourse::getEnabled, 1) - ); - - if (tenantCourse == null) { - // 检查是否通过套餐授权 - Long count = tenantPackageMapper.selectCount( - new LambdaQueryWrapper() - .eq(TenantPackage::getTenantId, tenantId) - .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE) - ); - - if (count == 0) { - log.warn("无权访问该课程,courseId={}, tenantId={}", courseId, tenantId); - throw new BusinessException("无权访问该课程"); - } - } - - List result = findByCourseId(courseId); - log.info("查询教师的课程环节成功,courseId={}, count={}", courseId, result.size()); - return result; - } + List findCourseLessonsForTeacher(Long courseId, Long tenantId); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ResourceLibraryService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ResourceLibraryService.java index 14a2357..dc290e3 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/ResourceLibraryService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ResourceLibraryService.java @@ -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 { - - private final ResourceLibraryMapper libraryMapper; - private final ResourceItemMapper itemMapper; +public interface ResourceLibraryService extends IService { /** * 分页查询资源库 */ - public Page findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize) { - log.info("查询资源库列表,libraryType={}, keyword={}, page={}, pageSize={}", libraryType, keyword, page, pageSize); - Page pageParam = new Page<>(page, pageSize); - LambdaQueryWrapper 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 result = libraryMapper.selectPage(pageParam, wrapper); - log.info("查询资源库列表成功,总数={}", result.getTotal()); - return result; - } + Page 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 wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(ResourceItem::getLibraryId, id); - itemMapper.delete(wrapper); - - libraryMapper.deleteById(id); - log.info("资源库删除成功,id={}", id); - } + void deleteLibrary(String id); /** * 分页查询资源项目 */ - public Page findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize) { - log.info("查询资源项目列表,libraryId={}, fileType={}, keyword={}, page={}, pageSize={}", libraryId, fileType, keyword, page, pageSize); - Page pageParam = new Page<>(page, pageSize); - LambdaQueryWrapper 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 result = itemMapper.selectPage(pageParam, wrapper); - log.info("查询资源项目列表成功,总数={}", result.getTotal()); - return result; - } + Page 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 ids) { - log.info("批量删除资源项目,ids={}", ids); - itemMapper.deleteBatchIds(ids); - log.info("批量删除资源项目成功,数量={}", ids.size()); - } + void batchDeleteItems(List ids); /** * 获取统计数据 */ - public Map getStats() { - log.info("获取资源库统计数据"); - Map stats = new HashMap<>(); - - Long libraryCount = libraryMapper.selectCount(null); - Long itemCount = itemMapper.selectCount(null); - - stats.put("totalLibraries", libraryCount); - stats.put("totalItems", itemCount); - - // 按资源库类型统计资源数量(绘本资源、教学材料等) - Map itemsByLibraryType = new HashMap<>(); - LambdaQueryWrapper libWrapper = new LambdaQueryWrapper<>(); - for (ResourceLibrary lib : libraryMapper.selectList(libWrapper)) { - String type = lib.getLibraryType(); - if (type != null) { - LambdaQueryWrapper 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 getStats(); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java index ade540a..9a4ef77 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java @@ -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 { - - private final ThemeMapper themeMapper; - private final CoursePackageMapper courseMapper; +public interface ThemeService extends IService { /** * 查询所有主题 */ - public List findAll() { - log.info("查询所有主题"); - List result = lambdaQuery() - .orderByAsc(Theme::getSortOrder) - .list(); - log.info("查询所有主题成功,count={}", result.size()); - return result; - } + List 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().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 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 ids); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java new file mode 100644 index 0000000..f84cd07 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java @@ -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 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 findTenantCollections(Long tenantId) { + log.info("查询租户课程套餐,tenantId={}", tenantId); + + // 查询租户的课程套餐关联 + List tenantPackages = tenantPackageMapper.selectList( + new LambdaQueryWrapper() + .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 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 pageCollections(Integer pageNum, Integer pageSize, String status) { + log.info("分页查询课程套餐,pageNum={}, pageSize={}, status={}", pageNum, pageSize, status); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(status)) { + wrapper.eq(CourseCollection::getStatus, status); + } + wrapper.orderByDesc(CourseCollection::getCreatedAt); + + Page page = collectionMapper.selectPage( + new Page<>(pageNum, pageSize), + wrapper + ); + + // 转换为响应对象 + List responses = page.getRecords().stream() + .map(this::toResponse) + .collect(Collectors.toList()); + + Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(responses); + return result; + } + + /** + * 获取课程套餐下的课程包列表 + */ + @Override + public List getPackagesByCollection(Long collectionId) { + log.info("获取课程套餐的课程包列表,collectionId={}", collectionId); + + // 查询关联关系 + List associations = collectionPackageMapper.selectList( + new LambdaQueryWrapper() + .eq(CourseCollectionPackage::getCollectionId, collectionId) + .orderByAsc(CourseCollectionPackage::getSortOrder) + ); + + if (associations.isEmpty()) { + return new ArrayList<>(); + } + + // 获取课程包详情 + List packageIds = associations.stream() + .map(CourseCollectionPackage::getPackageId) + .collect(Collectors.toList()); + + List packages = packageMapper.selectList( + new LambdaQueryWrapper() + .in(CoursePackage::getId, packageIds) + .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode()) + ); + + // 转换为响应对象并设置排序号 + List 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 packageIds) { + log.info("设置课程套餐的课程包,collectionId={}, packageCount={}", collectionId, packageIds.size()); + + // 验证课程套餐是否存在 + CourseCollection collection = collectionMapper.selectById(collectionId); + if (collection == null) { + throw new BusinessException("课程套餐不存在"); + } + + // 去重:保持首次出现顺序,避免 uk_collection_package 唯一约束冲突 + List distinctIds = packageIds.stream().distinct().collect(Collectors.toList()); + + // 验证课程包是否存在(应用层外键约束) + if (!distinctIds.isEmpty()) { + List packages = packageMapper.selectList( + new LambdaQueryWrapper() + .in(CoursePackage::getId, distinctIds) + ); + if (packages.size() != distinctIds.size()) { + throw new BusinessException("存在无效的课程包 ID"); + } + } + + // 删除旧的关联 + collectionPackageMapper.delete( + new LambdaQueryWrapper() + .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() + .eq(TenantPackage::getCollectionId, id) + .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE) + ); + + if (tenantCount > 0) { + log.warn("删除课程套餐失败,有 {} 个租户正在使用此套餐,id={}", tenantCount, id); + throw new BusinessException("有 " + tenantCount + " 个租户正在使用此课程套餐,无法删除"); + } + + // 清理所有状态的租户套餐关联记录(包括非活跃状态) + List allTenantPackages = tenantPackageMapper.selectList( + new LambdaQueryWrapper() + .eq(TenantPackage::getCollectionId, id) + ); + + if (!allTenantPackages.isEmpty()) { + tenantPackageMapper.delete( + new LambdaQueryWrapper() + .eq(TenantPackage::getCollectionId, id) + ); + log.info("已清理 {} 条租户套餐关联记录", allTenantPackages.size()); + } + + // 删除课程套餐与课程包的关联关系 + List collectionPackages = collectionPackageMapper.selectList( + new LambdaQueryWrapper() + .eq(CourseCollectionPackage::getCollectionId, id) + ); + + if (!collectionPackages.isEmpty()) { + collectionPackageMapper.delete( + new LambdaQueryWrapper() + .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 existingPackages = tenantPackageMapper.selectList( + new LambdaQueryWrapper() + .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 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 courseItems = new ArrayList<>(); + + // 查询课程包关联的课程环节 + List 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(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseLessonServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseLessonServiceImpl.java new file mode 100644 index 0000000..26d07b3 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseLessonServiceImpl.java @@ -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 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 findByCourseId(Long courseId) { + log.info("查询课程的所有环节,courseId={}", courseId); + List result = courseLessonMapper.selectList( + new LambdaQueryWrapper() + .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() + .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() + .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 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 findSteps(Long lessonId) { + log.info("查询课程环节的教学环节,lessonId={}", lessonId); + List result = lessonStepMapper.selectList( + new LambdaQueryWrapper() + .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 resourceIds) { + log.info("创建教学环节,lessonId={}, name={}", lessonId, name); + // 获取最大排序号 + Integer maxSortOrder = lessonStepMapper.selectList( + new LambdaQueryWrapper() + .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 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() + .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 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 findCourseLessonsForTeacher(Long courseId, Long tenantId) { + log.info("查询教师的课程环节,courseId={}, tenantId={}", courseId, tenantId); + // 检查租户是否有权限访问该课程 + TenantCourse tenantCourse = tenantCourseMapper.selectOne( + new LambdaQueryWrapper() + .eq(TenantCourse::getTenantId, tenantId) + .eq(TenantCourse::getCourseId, courseId) + .eq(TenantCourse::getEnabled, 1) + ); + + if (tenantCourse == null) { + // 检查是否通过套餐授权 + Long count = tenantPackageMapper.selectCount( + new LambdaQueryWrapper() + .eq(TenantPackage::getTenantId, tenantId) + .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE) + ); + + if (count == 0) { + log.warn("无权访问该课程,courseId={}, tenantId={}", courseId, tenantId); + throw new BusinessException("无权访问该课程"); + } + } + + List result = findByCourseId(courseId); + log.info("查询教师的课程环节成功,courseId={}, count={}", courseId, result.size()); + return result; + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/ResourceLibraryServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ResourceLibraryServiceImpl.java new file mode 100644 index 0000000..78f660c --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ResourceLibraryServiceImpl.java @@ -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 implements ResourceLibraryService { + + private final ResourceLibraryMapper libraryMapper; + private final ResourceItemMapper itemMapper; + + /** + * 分页查询资源库 + */ + @Override + public Page findAllLibraries(String libraryType, String keyword, Integer page, Integer pageSize) { + log.info("查询资源库列表,libraryType={}, keyword={}, page={}, pageSize={}", libraryType, keyword, page, pageSize); + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper 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 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 wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ResourceItem::getLibraryId, id); + itemMapper.delete(wrapper); + + libraryMapper.deleteById(id); + log.info("资源库删除成功,id={}", id); + } + + /** + * 分页查询资源项目 + */ + @Override + public Page findAllItems(String libraryId, String fileType, String keyword, Integer page, Integer pageSize) { + log.info("查询资源项目列表,libraryId={}, fileType={}, keyword={}, page={}, pageSize={}", libraryId, fileType, keyword, page, pageSize); + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper 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 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 ids) { + log.info("批量删除资源项目,ids={}", ids); + itemMapper.deleteBatchIds(ids); + log.info("批量删除资源项目成功,数量={}", ids.size()); + } + + /** + * 获取统计数据 + */ + @Override + public Map getStats() { + log.info("获取资源库统计数据"); + Map stats = new HashMap<>(); + + Long libraryCount = libraryMapper.selectCount(null); + Long itemCount = itemMapper.selectCount(null); + + stats.put("totalLibraries", libraryCount); + stats.put("totalItems", itemCount); + + // 按资源库类型统计资源数量(绘本资源、教学材料等) + Map itemsByLibraryType = new HashMap<>(); + LambdaQueryWrapper libWrapper = new LambdaQueryWrapper<>(); + for (ResourceLibrary lib : libraryMapper.selectList(libWrapper)) { + String type = lib.getLibraryType(); + if (type != null) { + LambdaQueryWrapper 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; + } + +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/ThemeServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ThemeServiceImpl.java new file mode 100644 index 0000000..3f0f815 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ThemeServiceImpl.java @@ -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 implements ThemeService { + + private final ThemeMapper themeMapper; + private final CoursePackageMapper courseMapper; + + /** + * 查询所有主题 + */ + @Override + public List findAll() { + log.info("查询所有主题"); + List 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().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 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("主题重新排序成功"); + } +}