package com.estate.expert.admin.service.common; import com.estate.expert.admin.domain.common.EntityBase; import com.estate.expert.admin.domain.common.EntityBaseRepository; import com.estate.expert.admin.domain.common.IDtoBase; import com.estate.expert.admin.domain.common.mapper.EntityRefMapper; import com.estate.expert.admin.domain.common.mapper.IMapperBase; import com.estate.expert.admin.domain.user.User; import com.estate.expert.admin.enums.BizResultCode; import com.estate.expert.admin.utils.DeletedObjectUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import javax.persistence.EntityManager; import javax.transaction.Transactional; import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.StreamSupport; /** * 基础的Service实现 * 包含了增删改查的基本操作 * 避免重复编写基础的增删改查代码以及不必要的测试 */ @Slf4j @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public abstract class CrudServiceBaseImpl implements CrudServiceBase { @Autowired protected EntityBaseRepository entityRepository; @Autowired protected IMapperBase entityMapper; @Autowired protected EntityRefMapper entityRefMapper; @Autowired protected EntityManager entityManager; /** * 不要重载这个方法,这只是个壳 * 所有对于实体的操作放在 add(User creator, T entity) 方法中 * 重载它来实现对实体的操作 */ @Override public T add(User creator, D dto) { return add(creator, toEntity(dto)); } @Override public void addDtos(User creator, List dtoList) { var entityList = dtoList.stream().map(this::toEntity).toList(); addEntities(creator, entityList); } @Override public void addEntities(User creator, List entityList) { var now = LocalDateTime.now(); for(var entity : entityList) { if(entity.getName() == null) entity.setName(UUID.randomUUID().toString()); entity.setCreator(creator); if(entity.getCreateTime() == null) entity.setCreateTime(now); if(entity.getUpdateTime() == null) entity.setUpdateTime(now); } // 这块只有初始化的时候会用,所以不去检查字段,以提高性能 // 正常要批量导入的话,应该检查字段 // 可以循环调用add方法 entityRepository.saveAll(entityList); } /** * 批量导入实体(自动处理重名) * 专门用于 Excel 导入等批量导入场景 * * @param creator 创建者 * @param entityList 实体列表 */ @Transactional public void importEntities(User creator, List entityList) { if(entityList == null || entityList.isEmpty()) { return; } LocalDateTime now = LocalDateTime.now(); Set usedNames = new HashSet<>(); for(T entity : entityList) { String originalName = entity.getName(); if(originalName == null || originalName.trim().isEmpty()) { originalName = "未命名"; } // 如果名称重复,自动在后面加数字 String finalName = originalName; int suffix = 1; while(entityRepository.existsByName(finalName) || usedNames.contains(finalName)) { finalName = originalName + "-" + suffix; suffix++; } usedNames.add(finalName); entity.setName(finalName); entity.setId(null); entity.setCreator(creator); if(entity.getCreateTime() == null) { entity.setCreateTime(now); } if(entity.getUpdateTime() == null) { entity.setUpdateTime(now); } // 调用子类的钩子方法,用于处理特定逻辑(如更新地理位置) beforeImport(entity); } // 批量保存 entityRepository.saveAll(entityList); log.info("成功导入 {} 个 {}", entityList.size(), entityList.get(0).getClass().getSimpleName()); } /** * 导入前的钩子方法 * 子类可以重写此方法,在批量导入前对实体进行额外处理 * * @param entity 待导入的实体 */ protected void beforeImport(T entity) { // 默认不做任何处理,子类可以重写 } @Override public T add(User creator, T entity) { BizResultCode.ENTITY_NAME_ALREADY_EXISTS.check(entityRepository.existsByName(entity.getName())); entity.setId(null); entity.setCreator(creator); entity.setCreateTime(LocalDateTime.now()); entity.setUpdateTime(LocalDateTime.now()); return save(entity); } @Override public T edit(D dto) { BizResultCode.ID_CANNOT_NULL.check(dto.getId() == null); var entity = get(dto.getId()); BizResultCode.ENTITY_NOT_FOUND.check(entity == null); entityMapper.partialUpdate(dto, entity); return edit(entity); } @Override public T edit(T entity) { var otherEntity = entityRepository.findByName(entity.getName()); BizResultCode.ENTITY_NAME_ALREADY_EXISTS.check(otherEntity!=null && !otherEntity.getId().equals(entity.getId())); entity.setUpdateTime(LocalDateTime.now()); return save(entity); } protected void beforeDelete(T entity) { } @Transactional @Override public void deleteAllById(Iterable request) { var entities = entityRepository.findAllById(request); entities.forEach(entity -> { beforeDelete(entity); entity.setDeleted(true); entity.setName(DeletedObjectUtil.toDeletedString(entity.getName())); }); entityRepository.saveAll(entities); } @Override public void deletePhysical(Integer id) { entityRepository.deleteById(id); } @Override public T get(Integer id) { BizResultCode.ID_CANNOT_NULL.check(id == null); var entity = entityRepository.findById(id); if(entity.isEmpty() || entity.get().isDeleted()) return null; return entity.get(); } @Override public T get(Integer id, boolean withDeleted) { BizResultCode.ID_CANNOT_NULL.check(id == null); var entity = entityRepository.findById(id); if(entity.isEmpty() || (entity.get().isDeleted() && !withDeleted)) return null; return entity.get(); } @Override public D detail(Integer id) { var entity = get(id); BizResultCode.ENTITY_NOT_FOUND.check(entity == null); return toDto(entity); } @Override public T save(T entity) { BizResultCode.ENTITY_CANNOT_NULL.check(entity == null); return entityRepository.save(entity); } @Override public T clone(Integer id) { var entity = get(id); BizResultCode.ENTITY_NOT_FOUND.check(entity == null); entityManager.detach(entity); entity.setId(null); var name = entity.getName(); do { name = "%s - %d".formatted(name, System.currentTimeMillis()); } while (entityRepository.existsByName(name)); entity.setName(name); return add(entity.getCreator(), entity); } @Override public List findAll() { return StreamSupport.stream(entityRepository.findAll().spliterator(), false) .filter(t -> !t.isDeleted()).toList(); } @Override public List findAllById(Iterable ids) { return StreamSupport.stream(entityRepository.findAllById(ids).spliterator(), false) .filter(t -> !t.isDeleted()).toList(); } @Override public List toDto(List entityList) { return entityList.stream().map(this::toDto).toList(); } @Override public D toDto(T entity) { if(entity == null) return null; entity.setName(DeletedObjectUtil.beautifyDeletedString(entity.getName())); return entityMapper.toDto(entity); } @Override public T toEntity(D dto) { return entityMapper.toEntity(dto); } @Override public T getByName(String name) { return entityRepository.findByName(name); } @Override public boolean existsById(Integer id) { return entityRepository.existsById(id); } @Override public boolean existsByName(String name) { return entityRepository.existsByName(name); } }