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.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.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 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.List;
|
|
@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<HealthCourseListItemVO> page(int page,
|
int pageSize,
|
String keyword,
|
Long levelCategoryId,
|
Long difficultyCategoryId,
|
Integer status) {
|
IPage<HealthCourseListRow> result = healthCourseMapper.selectCoursePage(
|
new Page<HealthCourseListRow>(page, pageSize),
|
trimToNull(keyword),
|
levelCategoryId,
|
difficultyCategoryId,
|
status
|
);
|
List<HealthCourseListItemVO> list = new ArrayList<HealthCourseListItemVO>();
|
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<HealthCourseVideo> videos = healthCourseVideoMapper.selectList(new LambdaQueryWrapper<HealthCourseVideo>()
|
.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<HealthCourseListRow> rows = healthCourseMapper.selectCourseList(
|
trimToNull(keyword),
|
levelCategoryId,
|
difficultyCategoryId,
|
status
|
);
|
List<HealthCourseExportRow> exports = new ArrayList<HealthCourseExportRow>();
|
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 PageResponse<HealthCourseVideoItemVO> pageVideos(Long courseId,
|
int page,
|
int pageSize,
|
String chapterName,
|
Integer saleType) {
|
requireCourse(courseId);
|
LambdaQueryWrapper<HealthCourseVideo> qw = new LambdaQueryWrapper<HealthCourseVideo>()
|
.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 (saleType != null) {
|
qw.eq(HealthCourseVideo::getSaleType, saleType);
|
}
|
IPage<HealthCourseVideo> result = healthCourseVideoMapper.selectPage(new Page<HealthCourseVideo>(page, pageSize), qw);
|
List<HealthCourseVideoItemVO> list = new ArrayList<HealthCourseVideoItemVO>();
|
for (HealthCourseVideo item : result.getRecords()) {
|
list.add(toVideoItem(item));
|
}
|
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) {
|
requireCourse(courseId);
|
validateVideoSale(req);
|
HealthCourseVideo next = new HealthCourseVideo();
|
next.setCourseId(courseId);
|
fillVideo(next, req);
|
next.setSort(resolveNextVideoSort(courseId));
|
next.setDeleted(0);
|
healthCourseVideoMapper.insert(next);
|
refreshCourseStats(courseId);
|
return detailVideo(courseId, next.getId());
|
}
|
|
@Transactional
|
public HealthCourseVideoDetailVO updateVideo(Long courseId, Long videoId, HealthCourseVideoSaveRequest req) {
|
validateVideoSale(req);
|
HealthCourseVideo video = requireVideo(courseId, videoId);
|
fillVideo(video, req);
|
healthCourseVideoMapper.updateById(video);
|
refreshCourseStats(courseId);
|
return detailVideo(courseId, videoId);
|
}
|
|
@Transactional
|
public void deleteVideo(Long courseId, Long videoId) {
|
requireVideo(courseId, videoId);
|
healthCourseVideoMapper.deleteById(videoId);
|
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.setChapterName(req.getChapterName().trim());
|
video.setChapterRemark(trimToNull(req.getChapterRemark()));
|
video.setVideoUrl(req.getVideoUrl().trim());
|
video.setDurationSeconds(req.getDurationSeconds());
|
video.setSaleType(req.getSaleType());
|
video.setSalePrice(req.getSaleType() != null && req.getSaleType() == 0 ? BigDecimal.ZERO : req.getSalePrice());
|
video.setPlayCount(req.getPlayCount());
|
video.setStatus(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<LiveCourseCategory>()
|
.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<Expert>()
|
.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 validateVideoSale(HealthCourseVideoSaveRequest req) {
|
if (req.getSaleType() == null || (req.getSaleType() != 0 && req.getSaleType() != 1)) {
|
throw new IllegalArgumentException("售价类型不合法");
|
}
|
if (req.getSaleType() == 0 && req.getSalePrice() != null && req.getSalePrice().compareTo(BigDecimal.ZERO) != 0) {
|
throw new IllegalArgumentException("免费课程售价必须为0");
|
}
|
}
|
|
private int resolveNextVideoSort(Long courseId) {
|
HealthCourseVideo latest = healthCourseVideoMapper.selectOne(new LambdaQueryWrapper<HealthCourseVideo>()
|
.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<HealthCourseVideo> videos = healthCourseVideoMapper.selectList(new LambdaQueryWrapper<HealthCourseVideo>()
|
.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 = healthCourseVideoMapper.selectById(videoId);
|
if (video == null) {
|
throw new IllegalArgumentException("视频课程不存在");
|
}
|
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) {
|
HealthCourseVideoItemVO vo = new HealthCourseVideoItemVO();
|
vo.setId(item.getId());
|
vo.setCourseId(item.getCourseId());
|
vo.setChapterName(item.getChapterName());
|
vo.setChapterRemark(item.getChapterRemark());
|
vo.setVideoUrl(item.getVideoUrl());
|
vo.setDurationSeconds(item.getDurationSeconds());
|
vo.setSaleType(item.getSaleType());
|
vo.setSalePrice(item.getSalePrice());
|
vo.setPlayCount(item.getPlayCount());
|
vo.setSort(item.getSort());
|
vo.setStatus(item.getStatus());
|
vo.setCreatedAt(item.getCreatedAt());
|
return vo;
|
}
|
|
private HealthCourseVideoDetailVO toVideoDetail(HealthCourseVideo item) {
|
HealthCourseVideoDetailVO vo = new HealthCourseVideoDetailVO();
|
vo.setId(item.getId());
|
vo.setCourseId(item.getCourseId());
|
vo.setChapterName(item.getChapterName());
|
vo.setChapterRemark(item.getChapterRemark());
|
vo.setVideoUrl(item.getVideoUrl());
|
vo.setDurationSeconds(item.getDurationSeconds());
|
vo.setSaleType(item.getSaleType());
|
vo.setSalePrice(item.getSalePrice());
|
vo.setPlayCount(item.getPlayCount());
|
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 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;
|
}
|
}
|