package com.tcm.training.admin.live.service; import cn.idev.excel.FastExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tcm.training.admin.common.Jsons; import com.tcm.training.admin.common.PageResponse; import com.tcm.training.admin.live.dto.HealthCourseSaveRequest; import com.tcm.training.admin.live.dto.HealthCourseStatusRequest; import com.tcm.training.admin.live.dto.HealthCourseVideoSaveRequest; import com.tcm.training.admin.live.entity.Expert; import com.tcm.training.admin.live.entity.HealthCourse; import com.tcm.training.admin.live.entity.HealthCourseVideo; import com.tcm.training.admin.live.entity.LiveCourseCategory; import com.tcm.training.admin.live.excel.HealthCourseExportRow; import com.tcm.training.admin.live.excel.HealthCourseVideoImportRow; import com.tcm.training.admin.live.mapper.ExpertMapper; import com.tcm.training.admin.live.mapper.HealthCourseMapper; import com.tcm.training.admin.live.mapper.HealthCourseVideoMapper; import com.tcm.training.admin.live.mapper.LiveCourseCategoryMapper; import com.tcm.training.admin.live.vo.ExpertVO; import com.tcm.training.admin.live.vo.HealthCourseDetailRow; import com.tcm.training.admin.live.vo.HealthCourseDetailVO; import com.tcm.training.admin.live.vo.HealthCourseListItemVO; import com.tcm.training.admin.live.vo.HealthCourseListRow; import com.tcm.training.admin.live.vo.HealthCourseOptionVO; import com.tcm.training.admin.live.vo.HealthCourseStatusVO; import com.tcm.training.admin.live.vo.HealthCourseVideoDetailVO; import com.tcm.training.admin.live.vo.HealthCourseVideoItemVO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @Service public class HealthCourseAppService { private final HealthCourseMapper healthCourseMapper; private final HealthCourseVideoMapper healthCourseVideoMapper; private final LiveCourseCategoryMapper liveCourseCategoryMapper; private final ExpertMapper expertMapper; public HealthCourseAppService(HealthCourseMapper healthCourseMapper, HealthCourseVideoMapper healthCourseVideoMapper, LiveCourseCategoryMapper liveCourseCategoryMapper, ExpertMapper expertMapper) { this.healthCourseMapper = healthCourseMapper; this.healthCourseVideoMapper = healthCourseVideoMapper; this.liveCourseCategoryMapper = liveCourseCategoryMapper; this.expertMapper = expertMapper; } public PageResponse page(int page, int pageSize, String keyword, Long levelCategoryId, Long difficultyCategoryId, Integer status) { IPage result = healthCourseMapper.selectCoursePage( new Page(page, pageSize), trimToNull(keyword), levelCategoryId, difficultyCategoryId, status ); List list = new ArrayList(); for (HealthCourseListRow row : result.getRecords()) { list.add(toCourseListItem(row)); } return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize()); } public HealthCourseDetailVO detail(Long id) { HealthCourseDetailRow row = healthCourseMapper.selectCourseDetail(id); if (row == null) { throw new IllegalArgumentException("课程不存在"); } return toCourseDetail(row); } @Transactional public HealthCourseDetailVO create(HealthCourseSaveRequest req) { validateCategories(req.getLevelCategoryId(), req.getDifficultyCategoryId()); validateExpert(req.getExpertId()); HealthCourse course = new HealthCourse(); fillCourse(course, req); course.setRealSalesCount(0); course.setLearnUserCount(0); course.setFavoriteCount(0); course.setTotalDurationSeconds(0); course.setVideoCount(0); course.setSort(0); course.setDeleted(0); healthCourseMapper.insert(course); return detail(course.getId()); } @Transactional public HealthCourseDetailVO update(Long id, HealthCourseSaveRequest req) { validateCategories(req.getLevelCategoryId(), req.getDifficultyCategoryId()); validateExpert(req.getExpertId()); HealthCourse course = requireCourse(id); fillCourse(course, req); healthCourseMapper.updateById(course); return detail(id); } @Transactional public HealthCourseStatusVO patchStatus(Long id, HealthCourseStatusRequest req) { HealthCourse course = requireCourse(id); course.setStatus(req.getStatus()); healthCourseMapper.updateById(course); return new HealthCourseStatusVO(id, req.getStatus()); } @Transactional public void delete(Long id) { requireCourse(id); List videos = healthCourseVideoMapper.selectList(new LambdaQueryWrapper() .eq(HealthCourseVideo::getCourseId, id) .eq(HealthCourseVideo::getDeleted, 0)); for (HealthCourseVideo video : videos) { healthCourseVideoMapper.deleteById(video.getId()); } healthCourseMapper.deleteById(id); } public void export(HttpServletResponse response, String keyword, Long levelCategoryId, Long difficultyCategoryId, Integer status) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String filename = URLEncoder.encode("养生课程", "UTF-8").replace("+", "%20"); response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx"); List rows = healthCourseMapper.selectCourseList( trimToNull(keyword), levelCategoryId, difficultyCategoryId, status ); List exports = new ArrayList(); for (int i = 0; i < rows.size(); i++) { HealthCourseListRow row = rows.get(i); HealthCourseExportRow export = new HealthCourseExportRow(); export.setSerialNo(i + 1); export.setTitle(defaultText(row.getTitle())); export.setLevelName(defaultText(row.getLevelName())); export.setDifficultyName(defaultText(row.getDifficultyName())); export.setSalePrice(row.getSalePrice() == null ? BigDecimal.ZERO : row.getSalePrice()); export.setSalesCount(resolveSalesCount(row.getBaseSalesCount(), row.getRealSalesCount())); export.setLearnUserCount(defaultNumber(row.getLearnUserCount())); export.setTotalDuration(formatDuration(defaultNumber(row.getTotalDurationSeconds()))); export.setFavoriteCount(defaultNumber(row.getFavoriteCount())); export.setExpertName(defaultText(row.getExpertName())); export.setStatusText(statusText(row.getStatus())); exports.add(export); } FastExcel.write(response.getOutputStream(), HealthCourseExportRow.class) .sheet("养生课程") .doWrite(exports); } public List options(String keyword) { List courses = healthCourseMapper.selectList(new LambdaQueryWrapper() .eq(HealthCourse::getDeleted, 0) .like(keyword != null && !keyword.trim().isEmpty(), HealthCourse::getTitle, keyword == null ? null : keyword.trim()) .orderByAsc(HealthCourse::getSort) .orderByDesc(HealthCourse::getId)); List list = new ArrayList(); for (HealthCourse course : courses) { list.add(new HealthCourseOptionVO(course.getId(), course.getTitle())); } return list; } public PageResponse pageVideos(Long courseId, int page, int pageSize, String chapterName, Integer priceType) { requireCourse(courseId); LambdaQueryWrapper qw = new LambdaQueryWrapper() .eq(HealthCourseVideo::getCourseId, courseId) .eq(HealthCourseVideo::getDeleted, 0) .orderByAsc(HealthCourseVideo::getSort) .orderByAsc(HealthCourseVideo::getId); if (chapterName != null && !chapterName.trim().isEmpty()) { qw.like(HealthCourseVideo::getChapterName, chapterName.trim()); } if (priceType != null) { if (priceType == 0) { qw.eq(HealthCourseVideo::getSalePrice, BigDecimal.ZERO); } else if (priceType == 1) { qw.gt(HealthCourseVideo::getSalePrice, BigDecimal.ZERO); } } IPage result = healthCourseVideoMapper.selectPage(new Page(page, pageSize), qw); List list = new ArrayList(); for (HealthCourseVideo item : result.getRecords()) { list.add(toVideoItem(item)); } return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize()); } public PageResponse pageVideoResources(int page, int pageSize, String courseKeyword, Long courseId, Integer courseType, String chapterName, Integer priceType, Integer status) { List matchedCourseIds = resolveMatchedCourseIds(courseKeyword, courseId); if (matchedCourseIds.isEmpty()) { return PageResponse.of(Collections.emptyList(), 0, page, pageSize); } LambdaQueryWrapper qw = new LambdaQueryWrapper() .in(HealthCourseVideo::getCourseId, matchedCourseIds) .eq(HealthCourseVideo::getDeleted, 0) .eq(courseType != null, HealthCourseVideo::getCourseType, courseType) .eq(status != null, HealthCourseVideo::getStatus, status) .orderByAsc(HealthCourseVideo::getSort) .orderByDesc(HealthCourseVideo::getId); if (chapterName != null && !chapterName.trim().isEmpty()) { qw.like(HealthCourseVideo::getChapterName, chapterName.trim()); } if (priceType != null) { if (priceType == 0) { qw.eq(HealthCourseVideo::getSalePrice, BigDecimal.ZERO); } else if (priceType == 1) { qw.gt(HealthCourseVideo::getSalePrice, BigDecimal.ZERO); } } IPage result = healthCourseVideoMapper.selectPage(new Page(page, pageSize), qw); Map courseTitleMap = resolveCourseTitleMap(extractCourseIds(result.getRecords())); List list = new ArrayList(); for (HealthCourseVideo item : result.getRecords()) { list.add(toVideoItem(item, courseTitleMap.get(item.getCourseId()))); } return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize()); } public HealthCourseVideoDetailVO detailVideo(Long courseId, Long videoId) { return toVideoDetail(requireVideo(courseId, videoId)); } @Transactional public HealthCourseVideoDetailVO createVideo(Long courseId, HealthCourseVideoSaveRequest req) { req.setCourseId(courseId); return createVideoResource(req); } @Transactional public HealthCourseVideoDetailVO updateVideo(Long courseId, Long videoId, HealthCourseVideoSaveRequest req) { req.setCourseId(courseId); return updateVideoResource(videoId, req); } @Transactional public void deleteVideo(Long courseId, Long videoId) { requireVideo(courseId, videoId); healthCourseVideoMapper.deleteById(videoId); refreshCourseStats(courseId); } public HealthCourseVideoDetailVO detailVideoResource(Long id) { HealthCourseVideo video = requireVideoById(id); return toVideoDetail(video, requireCourse(video.getCourseId()).getTitle()); } @Transactional public HealthCourseVideoDetailVO createVideoResource(HealthCourseVideoSaveRequest req) { Long courseId = requireVideoCourseId(req.getCourseId()); validateVideo(req); HealthCourseVideo next = new HealthCourseVideo(); next.setCourseId(courseId); fillVideo(next, req); next.setSort(resolveNextVideoSort(courseId)); next.setDeleted(0); healthCourseVideoMapper.insert(next); refreshCourseStats(courseId); return detailVideoResource(next.getId()); } @Transactional public HealthCourseVideoDetailVO updateVideoResource(Long id, HealthCourseVideoSaveRequest req) { HealthCourseVideo video = requireVideoById(id); Long targetCourseId = req.getCourseId() == null ? video.getCourseId() : requireVideoCourseId(req.getCourseId()); req.setCourseId(targetCourseId); validateVideo(req); Long oldCourseId = video.getCourseId(); video.setCourseId(targetCourseId); fillVideo(video, req); healthCourseVideoMapper.updateById(video); refreshCourseStats(targetCourseId); if (!oldCourseId.equals(targetCourseId)) { refreshCourseStats(oldCourseId); } return detailVideoResource(id); } @Transactional public void deleteVideoResource(Long id) { HealthCourseVideo video = requireVideoById(id); healthCourseVideoMapper.deleteById(id); refreshCourseStats(video.getCourseId()); } public void downloadVideoResourceTemplate(HttpServletResponse response) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String filename = URLEncoder.encode("视频与课程资源导入模板", "UTF-8").replace("+", "%20"); response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx"); List rows = new ArrayList(); HealthCourseVideoImportRow sample = new HealthCourseVideoImportRow(); sample.setCourseId(1L); sample.setCourseTitle("中医针灸资格证"); sample.setCourseTypeText(courseTypeText(HealthCourseVideo.COURSE_TYPE_HEALTH)); sample.setChapterName("方剂学"); sample.setSalePrice(BigDecimal.ZERO); sample.setDurationSeconds(238); sample.setVideoUrl("https://example.com/video.mp4"); sample.setStatusText(statusText(1)); rows.add(sample); FastExcel.write(response.getOutputStream(), HealthCourseVideoImportRow.class) .sheet("视频资源导入模板") .doWrite(rows); } @Transactional public void importVideoResources(MultipartFile file) throws IOException { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("请选择导入文件"); } List rows = FastExcel.read(file.getInputStream()) .head(HealthCourseVideoImportRow.class) .sheet() .doReadSync(); if (rows == null || rows.isEmpty()) { throw new IllegalArgumentException("导入文件不能为空"); } Set touchedCourseIds = new LinkedHashSet(); int rowNum = 1; for (HealthCourseVideoImportRow row : rows) { rowNum++; if (row == null || isImportRowEmpty(row)) { continue; } validateImportRow(row, rowNum); HealthCourse course = requireCourse(row.getCourseId()); HealthCourseVideo video = new HealthCourseVideo(); video.setCourseId(course.getId()); video.setCourseType(HealthCourseVideo.COURSE_TYPE_HEALTH); video.setChapterName(row.getChapterName().trim()); video.setVideoSourceType(HealthCourseVideo.VIDEO_SOURCE_URL); video.setVideoUrl(row.getVideoUrl().trim()); video.setVideoFileName(null); video.setDurationSeconds(row.getDurationSeconds()); video.setSalePrice(row.getSalePrice() == null ? BigDecimal.ZERO : row.getSalePrice()); video.setSort(resolveNextVideoSort(course.getId())); video.setStatus(parseStatusText(row.getStatusText())); video.setDeleted(0); healthCourseVideoMapper.insert(video); touchedCourseIds.add(course.getId()); } if (touchedCourseIds.isEmpty()) { throw new IllegalArgumentException("导入文件没有可用数据"); } for (Long courseId : touchedCourseIds) { refreshCourseStats(courseId); } } private void fillCourse(HealthCourse course, HealthCourseSaveRequest req) { course.setTitle(req.getTitle().trim()); course.setLevelCategoryId(req.getLevelCategoryId()); course.setDifficultyCategoryId(req.getDifficultyCategoryId()); course.setExpertId(req.getExpertId()); course.setSalePrice(req.getSalePrice()); course.setSummary(trimToNull(req.getSummary())); course.setTargetUsers(trimToNull(req.getTargetUsers())); course.setCoverImg(req.getCoverImg().trim()); course.setBannerImgs(req.getBannerImgs()); course.setContent(req.getContent().trim()); course.setBaseSalesCount(req.getBaseSalesCount() == null ? 0 : req.getBaseSalesCount()); course.setStatus(req.getStatus()); course.setIsHomeRecommend(req.getIsHomeRecommend()); } private void fillVideo(HealthCourseVideo video, HealthCourseVideoSaveRequest req) { video.setCourseType(resolveCourseType(req.getCourseType())); video.setChapterName(req.getChapterName().trim()); video.setVideoSourceType(req.getVideoSourceType()); video.setVideoUrl(req.getVideoUrl().trim()); video.setVideoFileName(trimToNull(req.getVideoFileName())); video.setDurationSeconds(req.getDurationSeconds()); video.setSalePrice(req.getSalePrice()); video.setStatus(resolveVideoStatus(req.getStatus())); } private void validateCategories(Long levelCategoryId, Long difficultyCategoryId) { LiveCourseCategory level = requireCategory(levelCategoryId, LiveCourseCategoryAppService.TYPE_LEVEL, "养生等级"); LiveCourseCategory difficulty = requireCategory(difficultyCategoryId, LiveCourseCategoryAppService.TYPE_DIFFICULTY, "难度"); if (level == null || difficulty == null) { throw new IllegalArgumentException("分类不存在"); } } private LiveCourseCategory requireCategory(Long id, String type, String label) { LiveCourseCategory category = liveCourseCategoryMapper.selectOne(new LambdaQueryWrapper() .eq(LiveCourseCategory::getId, id) .eq(LiveCourseCategory::getType, type) .eq(LiveCourseCategory::getDeleted, 0) .last("limit 1")); if (category == null) { throw new IllegalArgumentException(label + "不存在"); } return category; } private void validateExpert(Long expertId) { Expert expert = expertMapper.selectOne(new LambdaQueryWrapper() .eq(Expert::getId, expertId) .eq(Expert::getDeleted, 0) .last("limit 1")); if (expert == null) { throw new IllegalArgumentException("授课专家不存在"); } if (expert.getStatus() == null || expert.getStatus() != 1) { throw new IllegalArgumentException("授课专家已禁用"); } } private void validateVideo(HealthCourseVideoSaveRequest req) { Integer courseType = resolveCourseType(req.getCourseType()); if (courseType != HealthCourseVideo.COURSE_TYPE_HEALTH) { throw new IllegalArgumentException("课程类型不合法"); } if (req.getCourseId() == null) { throw new IllegalArgumentException("请选择关联课程"); } if (req.getVideoSourceType() == null || (req.getVideoSourceType() != HealthCourseVideo.VIDEO_SOURCE_URL && req.getVideoSourceType() != HealthCourseVideo.VIDEO_SOURCE_UPLOAD)) { throw new IllegalArgumentException("视频来源不合法"); } if (req.getVideoUrl() == null || req.getVideoUrl().trim().isEmpty()) { throw new IllegalArgumentException("请输入视频地址"); } if (req.getSalePrice() != null && req.getSalePrice().compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("售价不能小于0"); } } private Integer resolveVideoStatus(Integer status) { return status == null ? 1 : status; } private int resolveNextVideoSort(Long courseId) { HealthCourseVideo latest = healthCourseVideoMapper.selectOne(new LambdaQueryWrapper() .eq(HealthCourseVideo::getCourseId, courseId) .eq(HealthCourseVideo::getDeleted, 0) .orderByDesc(HealthCourseVideo::getSort) .last("limit 1")); if (latest == null || latest.getSort() == null) { return 10; } return latest.getSort() + 10; } private void refreshCourseStats(Long courseId) { HealthCourse course = requireCourse(courseId); List videos = healthCourseVideoMapper.selectList(new LambdaQueryWrapper() .eq(HealthCourseVideo::getCourseId, courseId) .eq(HealthCourseVideo::getDeleted, 0)); int totalDurationSeconds = 0; for (HealthCourseVideo video : videos) { totalDurationSeconds += video.getDurationSeconds() == null ? 0 : video.getDurationSeconds(); } course.setVideoCount(videos.size()); course.setTotalDurationSeconds(totalDurationSeconds); healthCourseMapper.updateById(course); } private HealthCourse requireCourse(Long id) { HealthCourse course = healthCourseMapper.selectById(id); if (course == null) { throw new IllegalArgumentException("课程不存在"); } return course; } private HealthCourseVideo requireVideo(Long courseId, Long videoId) { requireCourse(courseId); HealthCourseVideo video = requireVideoById(videoId); if (!courseId.equals(video.getCourseId())) { throw new IllegalArgumentException("视频课程不属于当前课程"); } return video; } private HealthCourseListItemVO toCourseListItem(HealthCourseListRow row) { HealthCourseListItemVO vo = new HealthCourseListItemVO(); vo.setId(row.getId()); vo.setTitle(row.getTitle()); vo.setLevelName(row.getLevelName()); vo.setDifficultyName(row.getDifficultyName()); vo.setSalePrice(row.getSalePrice() == null ? BigDecimal.ZERO : row.getSalePrice()); vo.setSalesCount(resolveSalesCount(row.getBaseSalesCount(), row.getRealSalesCount())); vo.setLearnUserCount(defaultNumber(row.getLearnUserCount())); vo.setTotalDurationSeconds(defaultNumber(row.getTotalDurationSeconds())); vo.setFavoriteCount(defaultNumber(row.getFavoriteCount())); vo.setExpert(new ExpertVO(row.getExpertId(), row.getExpertName(), row.getExpertAvatar())); vo.setCoverImg(row.getCoverImg()); vo.setStatus(row.getStatus()); vo.setIsHomeRecommend(row.getIsHomeRecommend()); vo.setCreatedAt(row.getCreatedAt()); return vo; } private HealthCourseDetailVO toCourseDetail(HealthCourseDetailRow row) { HealthCourseDetailVO vo = new HealthCourseDetailVO(); vo.setId(row.getId()); vo.setTitle(row.getTitle()); vo.setLevelCategoryId(row.getLevelCategoryId()); vo.setLevelName(row.getLevelName()); vo.setDifficultyCategoryId(row.getDifficultyCategoryId()); vo.setDifficultyName(row.getDifficultyName()); vo.setExpert(new ExpertVO(row.getExpertId(), row.getExpertName(), row.getExpertAvatar())); vo.setSalePrice(row.getSalePrice() == null ? BigDecimal.ZERO : row.getSalePrice()); vo.setSummary(row.getSummary()); vo.setTargetUsers(row.getTargetUsers()); vo.setCoverImg(row.getCoverImg()); vo.setBannerImgs(Jsons.parseStringList(row.getBannerImgs())); vo.setContent(row.getContent()); vo.setBaseSalesCount(defaultNumber(row.getBaseSalesCount())); vo.setRealSalesCount(defaultNumber(row.getRealSalesCount())); vo.setSalesCount(resolveSalesCount(row.getBaseSalesCount(), row.getRealSalesCount())); vo.setLearnUserCount(defaultNumber(row.getLearnUserCount())); vo.setFavoriteCount(defaultNumber(row.getFavoriteCount())); vo.setTotalDurationSeconds(defaultNumber(row.getTotalDurationSeconds())); vo.setVideoCount(defaultNumber(row.getVideoCount())); vo.setStatus(row.getStatus()); vo.setIsHomeRecommend(row.getIsHomeRecommend()); vo.setSort(row.getSort()); vo.setCreatedAt(row.getCreatedAt()); vo.setUpdatedAt(row.getUpdatedAt()); return vo; } private HealthCourseVideoItemVO toVideoItem(HealthCourseVideo item) { return toVideoItem(item, null); } private HealthCourseVideoItemVO toVideoItem(HealthCourseVideo item, String courseTitle) { HealthCourseVideoItemVO vo = new HealthCourseVideoItemVO(); vo.setId(item.getId()); vo.setCourseType(resolveCourseType(item.getCourseType())); vo.setCourseTypeText(courseTypeText(item.getCourseType())); vo.setCourseId(item.getCourseId()); vo.setCourseTitle(defaultText(courseTitle)); vo.setChapterName(item.getChapterName()); vo.setVideoSourceType(item.getVideoSourceType()); vo.setVideoUrl(item.getVideoUrl()); vo.setVideoFileName(item.getVideoFileName()); vo.setDurationSeconds(item.getDurationSeconds()); vo.setDurationText(formatDuration(defaultNumber(item.getDurationSeconds()))); vo.setSalePrice(item.getSalePrice() == null ? BigDecimal.ZERO : item.getSalePrice()); vo.setWatchCount(0); vo.setSort(item.getSort()); vo.setStatus(item.getStatus()); vo.setStatusText(statusText(item.getStatus())); vo.setCreatedAt(item.getCreatedAt()); return vo; } private HealthCourseVideoDetailVO toVideoDetail(HealthCourseVideo item) { return toVideoDetail(item, null); } private HealthCourseVideoDetailVO toVideoDetail(HealthCourseVideo item, String courseTitle) { HealthCourseVideoDetailVO vo = new HealthCourseVideoDetailVO(); vo.setId(item.getId()); vo.setCourseType(resolveCourseType(item.getCourseType())); vo.setCourseTypeText(courseTypeText(item.getCourseType())); vo.setCourseId(item.getCourseId()); vo.setCourseTitle(defaultText(courseTitle)); vo.setChapterName(item.getChapterName()); vo.setVideoSourceType(item.getVideoSourceType()); vo.setVideoUrl(item.getVideoUrl()); vo.setVideoFileName(item.getVideoFileName()); vo.setDurationSeconds(item.getDurationSeconds()); vo.setSalePrice(item.getSalePrice() == null ? BigDecimal.ZERO : item.getSalePrice()); vo.setWatchCount(0); vo.setSort(item.getSort()); vo.setStatus(item.getStatus()); vo.setCreatedAt(item.getCreatedAt()); vo.setUpdatedAt(item.getUpdatedAt()); return vo; } private Integer resolveSalesCount(Integer baseSalesCount, Integer realSalesCount) { return defaultNumber(baseSalesCount) + defaultNumber(realSalesCount); } private Integer defaultNumber(Integer value) { return value == null ? 0 : value; } private String formatDuration(Integer seconds) { int totalSeconds = seconds == null ? 0 : seconds; int minutes = totalSeconds / 60; int remainSeconds = totalSeconds % 60; return minutes + ":" + String.format("%02d", remainSeconds); } private String statusText(Integer status) { return status != null && status == 1 ? "启用" : "禁用"; } private String courseTypeText(Integer courseType) { return resolveCourseType(courseType) == HealthCourseVideo.COURSE_TYPE_HEALTH ? "养生课程" : "-"; } private String defaultText(String text) { return text == null || text.trim().isEmpty() ? "-" : text; } private String trimToNull(String text) { if (text == null) { return null; } String trimmed = text.trim(); return trimmed.isEmpty() ? null : trimmed; } private Integer resolveCourseType(Integer courseType) { return courseType == null ? HealthCourseVideo.COURSE_TYPE_HEALTH : courseType; } private Long requireVideoCourseId(Long courseId) { return requireCourse(courseId).getId(); } private HealthCourseVideo requireVideoById(Long id) { HealthCourseVideo video = healthCourseVideoMapper.selectById(id); if (video == null) { throw new IllegalArgumentException("视频课程不存在"); } return video; } private List resolveMatchedCourseIds(String courseKeyword, Long courseId) { if (courseId != null) { return Collections.singletonList(requireCourse(courseId).getId()); } List courses = healthCourseMapper.selectList(new LambdaQueryWrapper() .eq(HealthCourse::getDeleted, 0) .like(courseKeyword != null && !courseKeyword.trim().isEmpty(), HealthCourse::getTitle, courseKeyword == null ? null : courseKeyword.trim())); if (courses.isEmpty()) { return Collections.emptyList(); } List ids = new ArrayList(); for (HealthCourse course : courses) { ids.add(course.getId()); } return ids; } private Map resolveCourseTitleMap(Collection courseIds) { if (courseIds == null || courseIds.isEmpty()) { return Collections.emptyMap(); } List courses = healthCourseMapper.selectList(new LambdaQueryWrapper() .in(HealthCourse::getId, courseIds) .eq(HealthCourse::getDeleted, 0)); Map map = new LinkedHashMap(); for (HealthCourse course : courses) { map.put(course.getId(), course.getTitle()); } return map; } private List extractCourseIds(List videos) { Set ids = new LinkedHashSet(); for (HealthCourseVideo video : videos) { ids.add(video.getCourseId()); } return new ArrayList(ids); } private boolean isImportRowEmpty(HealthCourseVideoImportRow row) { return row.getCourseId() == null && (row.getChapterName() == null || row.getChapterName().trim().isEmpty()) && row.getSalePrice() == null && row.getDurationSeconds() == null && (row.getVideoUrl() == null || row.getVideoUrl().trim().isEmpty()) && (row.getStatusText() == null || row.getStatusText().trim().isEmpty()); } private void validateImportRow(HealthCourseVideoImportRow row, int rowNum) { if (row.getCourseId() == null) { throw new IllegalArgumentException("第" + rowNum + "行:关联课程ID不能为空"); } if (row.getCourseTypeText() != null && !row.getCourseTypeText().trim().isEmpty() && !"养生课程".equals(row.getCourseTypeText().trim())) { throw new IllegalArgumentException("第" + rowNum + "行:课程类型仅支持养生课程"); } if (row.getChapterName() == null || row.getChapterName().trim().isEmpty()) { throw new IllegalArgumentException("第" + rowNum + "行:小节名称不能为空"); } if (row.getSalePrice() != null && row.getSalePrice().compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("第" + rowNum + "行:小节价格不能小于0"); } if (row.getDurationSeconds() == null || row.getDurationSeconds() < 0) { throw new IllegalArgumentException("第" + rowNum + "行:时长必须大于等于0"); } if (row.getVideoUrl() == null || row.getVideoUrl().trim().isEmpty()) { throw new IllegalArgumentException("第" + rowNum + "行:视频地址不能为空"); } try { parseStatusText(row.getStatusText()); } catch (IllegalArgumentException ex) { throw new IllegalArgumentException("第" + rowNum + "行:" + ex.getMessage()); } } private Integer parseStatusText(String statusText) { if (statusText == null || statusText.trim().isEmpty() || "启用".equals(statusText.trim())) { return 1; } if ("禁用".equals(statusText.trim())) { return 0; } throw new IllegalArgumentException("状态仅支持启用或禁用"); } }