Files
UserclassService/src/main/java/com/yunhai/bschclassmanage/service/impl/SchDicCourseService.java
T
2026-08-14 09:04:58 +08:00

467 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.bschclassmanage.service.impl;
import com.alibaba.fastjson.JSON;
import com.yunhai.bschclassmanage.Repository.*;
import com.yunhai.bschclassmanage.annotation.OperationLog;
import com.yunhai.bschclassmanage.entity.*;
import com.yunhai.bschclassmanage.enums.CourseType;
import com.yunhai.bschclassmanage.model.constant.LogModule;
import com.yunhai.bschclassmanage.model.dto.SchDicCourseDTO;
import com.yunhai.bschclassmanage.model.entityField.EntityFieldNames;
import com.yunhai.bschclassmanage.model.vo.PageVO;
import com.yunhai.bschclassmanage.model.vo.SchDicCourseVO;
import com.yunhai.bschclassmanage.service.ISchDicCourseService;
import com.yunhai.bschclassmanage.service.ISchDicDivisionService;
import com.yunhai.bschclassmanage.service.ISchDicMajorsService;
import com.yunhai.bschclassmanage.utils.*;
import com.yunhai.platform.utils.exception.DException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
public class SchDicCourseService implements ISchDicCourseService {
@Autowired
private SchDicCourseRepository schDicCourseRepository;
@Autowired
private SchDicCourseMajorsRepository schDicCourseMajorsRepository;
@Autowired
private SchDicCourseDivisionRepository schDicCourseDivisionRepository;
@Autowired
private ISchDicMajorsService schDicMajorsService;
@Autowired
private ISchDicDivisionService schDicDivisionService;
@Autowired
private RedisUtil redisUtil;
public List<SchDicCourse> getSchDicCourseList(Integer schId) {
Specification<SchDicCourse> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(EntityFieldNames.schId), schId));
return cb.and(predicates.toArray(new Predicate[0]));
};
return schDicCourseRepository.findAll(spec);
}
public List<SchDicCourse> getSchDicCourseCacheList(Integer schId) {
String cacheKey = RedisConstants.SCHOOL_COURSE + "_" + schId;
if (redisUtil.hasKey(cacheKey)) {
// 注意:返回副本或不可变列表,避免外部修改影响缓存
return redisUtil.getList(cacheKey,SchDicCourse.class);
}
// 同步查询数据库,防止缓存击穿
synchronized (this) {
if (redisUtil.hasKey(cacheKey)) {
return redisUtil.getList(cacheKey,SchDicCourse.class);
}
List<SchDicCourse> list = getSchDicCourseList(schId);
redisUtil.set(cacheKey, new ArrayList<>(list), RedisConstants.CACHE_EXPIRE_30D);
return list;
}
}
public PageVO<SchDicCourseVO> search(Integer schId, Integer type, String keyword,
Integer divisionId, Integer majorsId,
Pageable pageable) {
// 1. 查询课程分页(动态条件已在 Repository 中处理)
Page<SchDicCourse> coursePage = schDicCourseRepository.findCoursesByConditions(
schId, type, keyword, divisionId, majorsId, pageable
);
if (coursePage.isEmpty()) {
return PageVO.empty(pageable.getPageSize());
}
// 2. 提取课程 ID 列表
List<Integer> courseIds = coursePage.getContent().stream()
.map(SchDicCourse::getId)
.collect(Collectors.toList());
// 3. 批量加载关联关系(按课程ID分组)
Map<Integer, List<SchDicCourseMajors>> majorsGroup = loadMajorsGroup(courseIds, schId);
Map<Integer, List<SchDicCourseDivision>> divisionGroup = loadDivisionGroup(courseIds, schId);
// 4. 获取当前学校的所有专业和学部(用于填充实体详情)
Map<Integer, SchDicMajors> majorsMap = schDicMajorsService.getSchDicMajorsCacheList(schId)
.stream()
.collect(Collectors.toMap(SchDicMajors::getId, Function.identity()));
Map<Integer, SchDicDivision> divisionMap = schDicDivisionService.getSchDicDivisionCacheList(schId)
.stream()
.collect(Collectors.toMap(SchDicDivision::getId, Function.identity()));
// 5. 转换每个课程为 VO
List<SchDicCourseVO> voList = coursePage.getContent().stream()
.map(course -> convertToVO(course, majorsGroup, divisionGroup, majorsMap, divisionMap))
.collect(Collectors.toList());
return new PageVO<>(pageable.getPageNumber() + 1,
pageable.getPageSize(),
coursePage.getTotalElements(),
voList);
}
@OperationLog(module = LogModule.COURSE)
@Transactional(rollbackFor = Exception.class)
public void saveSchCourse(UserContext.UserInfo user, SchDicCourseDTO dto) {
Integer schId = user.getSchId();
// 1. 基础校验
if (dto == null) {
throw new DException("课程数据不能为空");
}
if (StringUtils.isBlank(dto.getName())) {
throw new DException("课程名称不能为空");
}
// 2. VO → Entity 映射
SchDicCourse entity = ModelMapperUtil.map(dto, SchDicCourse.class);
// 3. 名称唯一性校验
boolean isNew = (entity.getId() == null || entity.getId() == 0);
boolean nameExists = isNew ?
schDicCourseRepository.existsBySchIdAndName(schId, entity.getName()) :
schDicCourseRepository.existsBySchIdAndNameAndIdNot(schId, entity.getName(), entity.getId());
if (nameExists) {
throw new DException("课程名称已存在:" + entity.getName());
}
SchDicCourse oldEntity = null;
Set<Integer> oldMajors = null;
Set<Integer> oldDivisions = null;
// 4. 执行保存(新增或更新)
if (isNew) {
OtherUtil.setBaseEntityValue(user, entity); // 设置创建/更新审计字段
entity = schDicCourseRepository.save(entity);
} else {
SchDicCourse existing = schDicCourseRepository.findById(dto.getId())
.orElseThrow(() -> new DException("课程不存在,ID=" + dto.getId()));
oldEntity=OtherUtil.deepCopy(existing,SchDicCourse.class);
oldMajors = loadMajorsIdSet(dto.getId(),user.getSchId());
oldDivisions = loadDivisionsIdSet(dto.getId(),user.getSchId());
// 复制允许修改的字段(忽略 id, schId, 创建审计字段)
OtherUtil.setBaseEntityValue(user, entity); // 设置创建/更新审计字段
BeanUtils.copyProperties(entity, existing,
EntityFieldNames.id,
EntityFieldNames.schId,
EntityFieldNames.createDate,
EntityFieldNames.createUserId);
// 更新时需单独设置 updateDate / updateUserId(假设 setBaseEntityValue 已处理)
entity = schDicCourseRepository.save(existing);
}
// 5. 维护关联关系(删除旧关联 → 添加新关联)
Integer courseId = entity.getId();
// 5.1 删除旧的关联
deleteRelations(courseId, schId);
// 5.2 保存新的关联
saveRelations(courseId, schId, dto);
// 6. 清除缓存
String cacheKey = RedisConstants.SCHOOL_COURSE + "_" + schId;
redisUtil.del(cacheKey);
{
String description = buildCourseLogDescription(entity, isNew, oldEntity, dto,
oldMajors, oldDivisions,
schDicMajorsService.toIdAndNameMap(user.getSchId()),
schDicDivisionService.toIdAndNameMap(user.getSchId()));
LogContextHolder.setDescription(description);
}
}
/**
* 构建课程操作的详细日志描述
*/
private String buildCourseLogDescription(SchDicCourse newEntity, boolean isNew,
SchDicCourse oldEntity, SchDicCourseDTO dto,
Set<Integer> oldMajors, Set<Integer> oldDivisions,
Map<Integer, String> majorsNameMap,
Map<Integer, String> divisionNameMap) {
try {
List<String> parts = new ArrayList<>();
// ========== 1. 课程本身的变化 ==========
if (isNew) {
parts.add(String.format("新增课程:%s(编码:%s,类型:%s,别称:%s)",
newEntity.getName(),
newEntity.getCode() != null ? newEntity.getCode() : "无",
CourseType.nameById(newEntity.getType()),
newEntity.getNickname() != null ? newEntity.getNickname() : "无"));
} else {
List<String> changes = new ArrayList<>();
if (!Objects.equals(oldEntity.getName(), newEntity.getName())) {
changes.add(String.format("名称从 '%s' 修改为 '%s'", oldEntity.getName(), newEntity.getName()));
}
if (!Objects.equals(oldEntity.getCode(), newEntity.getCode())) {
changes.add(String.format("编码从 '%s' 修改为 '%s'", oldEntity.getCode(), newEntity.getCode()));
}
if (!Objects.equals(oldEntity.getNickname(), newEntity.getNickname())) {
changes.add(String.format("别称从 '%s' 修改为 '%s'", oldEntity.getNickname(), newEntity.getNickname()));
}
if (!Objects.equals(oldEntity.getType(), newEntity.getType())) {
changes.add(String.format("类型从 '%s' 修改为 '%s'",
CourseType.nameById(oldEntity.getType()),
CourseType.nameById(newEntity.getType())));
}
if (changes.isEmpty()) {
parts.add("课程信息无变更");
} else {
parts.add("课程变更:" + String.join(";", changes));
}
}
// ========== 2. 关联关系变化(专业) ==========
Set<Integer> newMajors = dto.getMajorsIds();
if (!isNew && oldMajors != null) {
Set<Integer> addedMajors = new HashSet<>(newMajors);
addedMajors.removeAll(oldMajors);
Set<Integer> removedMajors = new HashSet<>(oldMajors);
removedMajors.removeAll(newMajors);
if (!addedMajors.isEmpty()) {
parts.add("绑定专业:" + getNames(addedMajors, majorsNameMap));
}
if (!removedMajors.isEmpty()) {
parts.add("解绑专业:" + getNames(removedMajors, majorsNameMap));
}
} else if (isNew && newMajors != null && !newMajors.isEmpty()) {
parts.add("绑定专业:" + getNames(newMajors, majorsNameMap));
}
// ========== 3. 关联关系变化(学部) ==========
Set<Integer> newDivisions = dto.getDivisionIds();
if (!isNew && oldDivisions != null) {
Set<Integer> addedDivs = new HashSet<>(newDivisions);
addedDivs.removeAll(oldDivisions);
Set<Integer> removedDivs = new HashSet<>(oldDivisions);
removedDivs.removeAll(newDivisions);
if (!addedDivs.isEmpty()) {
parts.add("绑定学部:" + getNames(addedDivs, divisionNameMap));
}
if (!removedDivs.isEmpty()) {
parts.add("解绑学部:" + getNames(removedDivs, divisionNameMap));
}
} else if (isNew && newDivisions != null && !newDivisions.isEmpty()) {
parts.add("绑定学部:" + getNames(newDivisions, divisionNameMap));
}
return String.join(";", parts);
} catch (Exception e) {
// 日志构建失败时返回简短描述,不影响主流程
log.error("构建课程日志描述失败:{}", e.getMessage());
return isNew ? "新增课程(日志生成失败)" : "更新课程(日志生成失败)";
}
}
/**
* 将 Set<Integer> 转换为名称字符串(用"、"分隔)
*/
private String getNames(Set<Integer> ids, Map<Integer, String> nameMap) {
if (ids == null || ids.isEmpty()) return "无";
return ids.stream()
.map(id -> nameMap.getOrDefault(id, String.valueOf(id)))
.collect(Collectors.joining("、"));
}
@OperationLog(module = LogModule.COURSE)
@Transactional(rollbackFor = Exception.class)
public void delete(UserContext.UserInfo user,Integer id){
SchDicCourse entity= schDicCourseRepository.findByIdAndSchId(id,user.getSchId())
.orElseThrow(() -> new DException( "课程不存在"));
String cacheKey = RedisConstants.SCHOOL_COURSE + "_" + user.getSchId();
redisUtil.del(cacheKey);
deleteRelations(id, user.getSchId());
schDicCourseRepository.deleteById(id);
String description = String.format("删除课程:%s(编码:%s,类型:%s)",
entity.getName(),
entity.getCode() != null ? entity.getCode() : "无",
CourseType.nameById(entity.getType()));
LogContextHolder.setDescription(description);
log.debug("操作人:{},删除课程日志:{}", user.getUserName(), JSON.toJSONString(entity));
}
// ==================== 私有辅助方法 ====================
/**
* 批量删除课程-专业、课程-学部关联
*/
private void deleteRelations(Integer courseId, Integer schId) {
schDicCourseMajorsRepository.deleteByCourseIdAndSchId(courseId, schId);
schDicCourseDivisionRepository.deleteByCourseIdAndSchId(courseId, schId);
}
/**
* 批量保存新的课程-专业、课程-学部关联
*/
private void saveRelations(Integer courseId, Integer schId, SchDicCourseDTO dto) {
// 保存专业关联
if (CollectionUtils.isNotEmpty(dto.getMajorsIds())) {
List<SchDicCourseMajors> majorsList = dto.getMajorsIds().stream()
.filter(Objects::nonNull)
.map(id -> {
SchDicCourseMajors rel = new SchDicCourseMajors();
rel.setCourseId(courseId);
rel.setMajorsId(id);
rel.setSchId(schId);
return rel;
})
.collect(Collectors.toList());
if (!majorsList.isEmpty()) {
schDicCourseMajorsRepository.saveAll(majorsList);
}
}
// 保存学部关联
if (CollectionUtils.isNotEmpty(dto.getDivisionIds())) {
List<SchDicCourseDivision> divisionList = dto.getDivisionIds().stream()
.filter(Objects::nonNull)
.map(id -> {
SchDicCourseDivision rel = new SchDicCourseDivision();
rel.setCourseId(courseId);
rel.setDivisionId(id);
rel.setSchId(schId);
return rel;
})
.collect(Collectors.toList());
if (!divisionList.isEmpty()) {
schDicCourseDivisionRepository.saveAll(divisionList);
}
}
}
/**
* 批量加载课程-专业关联关系,并按 courseId 分组
*/
private Map<Integer, List<SchDicCourseMajors>> loadMajorsGroup(List<Integer> courseIds, Integer schId) {
if (courseIds.isEmpty()) {
return Collections.emptyMap();
}
return schDicCourseMajorsRepository.findByCourseIdInAndSchId(courseIds, schId)
.stream()
.collect(Collectors.groupingBy(SchDicCourseMajors::getCourseId));
}
/**
* 加载指定课程关联的专业列表(单个)
*/
private List<SchDicCourseMajors> loadMajorsList(Integer courseId, Integer schId) {
if (courseId == null) {
return Collections.emptyList();
}
return schDicCourseMajorsRepository.findByCourseIdAndSchId(courseId, schId);
}
/**
* 加载指定课程关联的专业ID集合(用于日志)
*/
private Set<Integer> loadMajorsIdSet(Integer courseId, Integer schId) {
return loadMajorsList(courseId, schId).stream()
.map(SchDicCourseMajors::getMajorsId)
.collect(Collectors.toSet());
}
/**
* 批量加载课程-学部关联关系,并按 courseId 分组
*/
private Map<Integer, List<SchDicCourseDivision>> loadDivisionGroup(List<Integer> courseIds, Integer schId) {
if (courseIds.isEmpty()) {
return Collections.emptyMap();
}
return schDicCourseDivisionRepository.findByCourseIdInAndSchId(courseIds, schId)
.stream()
.collect(Collectors.groupingBy(SchDicCourseDivision::getCourseId));
}
private List<SchDicCourseDivision> loadDivisionsList(Integer courseId, Integer schId) {
if (courseId == null) {
return Collections.emptyList();
}
return schDicCourseDivisionRepository.findByCourseIdAndSchId(courseId, schId);
}
private Set<Integer> loadDivisionsIdSet(Integer courseId, Integer schId) {
return loadDivisionsList(courseId, schId).stream()
.map(SchDicCourseDivision::getDivisionId)
.collect(Collectors.toSet());
}
/**
* 将单个课程实体转换为 VO,并填充关联列表
*/
private SchDicCourseVO convertToVO(SchDicCourse course,
Map<Integer, List<SchDicCourseMajors>> majorsGroup,
Map<Integer, List<SchDicCourseDivision>> divisionGroup,
Map<Integer, SchDicMajors> majorsEntityMap,
Map<Integer, SchDicDivision> divisionEntityMap) {
// 使用 ModelMapperUtil 复制基础字段
SchDicCourseVO vo = ModelMapperUtil.map(course, SchDicCourseVO.class);
if (vo == null&& course != null) throw new DException("数据转换失败");
// 填充 typeName
vo.setTypeName(CourseType.nameById(course.getType()));
// 填充 majorsList
List<SchDicCourseMajors> majorsRel = majorsGroup.getOrDefault(course.getId(), Collections.emptyList());
List<SchDicMajors> majors = majorsRel.stream()
.map(rel -> majorsEntityMap.get(rel.getMajorsId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 按 sort 升序,null 值排在最后
majors.sort(Comparator.comparing(SchDicMajors::getSort, Comparator.nullsLast(Comparator.naturalOrder())));
vo.setMajorsList(majors);
// 填充 divisionList
List<SchDicCourseDivision> divisionRel = divisionGroup.getOrDefault(course.getId(), Collections.emptyList());
List<SchDicDivision> divisions = divisionRel.stream()
.map(rel -> divisionEntityMap.get(rel.getDivisionId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 按 sort 升序,null 值排在最后
divisions.sort(Comparator.comparing(SchDicDivision::getSort, Comparator.nullsLast(Comparator.naturalOrder())));
vo.setDivisionList(divisions);
return vo;
}
}