zhou zhou
15 小时以前 82624affb0251b75b62b35567d3eb260c06efe78
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamServiceImpl.java
@@ -4,23 +4,30 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.dto.AiChatModelOptionDto;
import com.vincent.rsf.server.ai.dto.AiParamValidateResultDto;
import com.vincent.rsf.server.ai.entity.AiParam;
import com.vincent.rsf.server.ai.mapper.AiParamMapper;
import com.vincent.rsf.server.ai.store.AiConfigCacheStore;
import com.vincent.rsf.server.ai.store.AiConversationCacheStore;
import com.vincent.rsf.server.ai.service.AiParamService;
import com.vincent.rsf.server.system.enums.StatusType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Service("aiParamService")
@RequiredArgsConstructor
public class AiParamServiceImpl extends ServiceImpl<AiParamMapper, AiParam> implements AiParamService {
    private final AiParamValidationSupport aiParamValidationSupport;
    private final AiConfigCacheStore aiConfigCacheStore;
    private final AiConversationCacheStore aiConversationCacheStore;
    @Override
    public AiParam getActiveParam(Long tenantId) {
@@ -34,6 +41,68 @@
            throw new CoolException("未找到启用中的 AI 参数配置");
        }
        return aiParam;
    }
    @Override
    public AiParam getChatParam(Long tenantId, Long aiParamId) {
        ensureTenantId(tenantId);
        if (aiParamId == null) {
            return getActiveParam(tenantId);
        }
        AiParam aiParam = requireOwnedRecord(aiParamId, tenantId);
        if (!AiDefaults.PARAM_VALIDATE_VALID.equals(aiParam.getValidateStatus())) {
            throw new CoolException("所选 AI 模型未通过校验,暂不可用于对话");
        }
        return aiParam;
    }
    @Override
    public List<AiChatModelOptionDto> listChatModelOptions(Long tenantId) {
        ensureTenantId(tenantId);
        List<AiParam> params = this.list(new LambdaQueryWrapper<AiParam>()
                .eq(AiParam::getTenantId, tenantId)
                .eq(AiParam::getDeleted, 0)
                .eq(AiParam::getValidateStatus, AiDefaults.PARAM_VALIDATE_VALID)
                .orderByDesc(AiParam::getStatus)
                .orderByDesc(AiParam::getUpdateTime)
                .orderByDesc(AiParam::getCreateTime)
                .orderByDesc(AiParam::getId));
        if (params.isEmpty()) {
            return List.of(toChatModelOption(getActiveParam(tenantId)));
        }
        return params.stream()
                .map(this::toChatModelOption)
                .toList();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AiParam setDefaultParam(Long id, Long tenantId, Long userId) {
        ensureTenantId(tenantId);
        if (id == null) {
            throw new CoolException("AI 参数 ID 不能为空");
        }
        AiParam target = requireOwnedRecord(id, tenantId);
        if (!AiDefaults.PARAM_VALIDATE_VALID.equals(target.getValidateStatus())) {
            throw new CoolException("仅允许将校验通过的 AI 参数设置为默认");
        }
        Date now = new Date();
        this.lambdaUpdate()
                .eq(AiParam::getTenantId, tenantId)
                .eq(AiParam::getDeleted, 0)
                .set(AiParam::getStatus, StatusType.DISABLE.val)
                .set(AiParam::getUpdateBy, userId)
                .set(AiParam::getUpdateTime, now)
                .update();
        target.setStatus(StatusType.ENABLE.val);
        target.setUpdateBy(userId);
        target.setUpdateTime(now);
        if (!super.updateById(target)) {
            throw new CoolException("设置默认 AI 参数失败");
        }
        aiConfigCacheStore.evictTenantConfigCaches(tenantId);
        aiConversationCacheStore.evictTenantRuntimeCaches(tenantId);
        return target;
    }
    @Override
@@ -56,6 +125,7 @@
        AiParam current = requireOwnedRecord(aiParam.getId(), tenantId);
        aiParam.setTenantId(current.getTenantId());
        ensureBaseFields(aiParam);
        ensureDefaultStillExists(tenantId, current, aiParam.getStatus());
        ensureSingleActive(tenantId, aiParam.getId(), aiParam.getStatus());
        applyValidation(aiParam);
    }
@@ -66,6 +136,48 @@
        fillDefaults(aiParam);
        ensureBaseFields(aiParam);
        return aiParamValidationSupport.validate(aiParam);
    }
    @Override
    public boolean save(AiParam entity) {
        boolean saved = super.save(entity);
        if (saved && entity != null && entity.getTenantId() != null) {
            aiConfigCacheStore.evictTenantConfigCaches(entity.getTenantId());
            aiConversationCacheStore.evictTenantRuntimeCaches(entity.getTenantId());
        }
        return saved;
    }
    @Override
    public boolean updateById(AiParam entity) {
        boolean updated = super.updateById(entity);
        if (updated && entity != null && entity.getTenantId() != null) {
            aiConfigCacheStore.evictTenantConfigCaches(entity.getTenantId());
            aiConversationCacheStore.evictTenantRuntimeCaches(entity.getTenantId());
        }
        return updated;
    }
    @Override
    public boolean removeByIds(java.util.Collection<?> list) {
        java.util.List<java.io.Serializable> ids = list == null ? java.util.List.of() : list.stream()
                .filter(java.util.Objects::nonNull)
                .map(item -> (java.io.Serializable) item)
                .toList();
        java.util.List<AiParam> records = this.listByIds(ids);
        ensureRemovingDefaultIsSafe(records);
        boolean removed = super.removeByIds(list);
        if (removed) {
            records.stream()
                    .map(AiParam::getTenantId)
                    .filter(java.util.Objects::nonNull)
                    .distinct()
                    .forEach(tenantId -> {
                        aiConfigCacheStore.evictTenantConfigCaches(tenantId);
                        aiConversationCacheStore.evictTenantRuntimeCaches(tenantId);
                    });
        }
        return removed;
    }
    private void ensureBaseFields(AiParam aiParam) {
@@ -99,6 +211,44 @@
        }
        if (this.count(wrapper) > 0) {
            throw new CoolException("同一租户仅允许一条启用中的 AI 参数配置");
        }
    }
    private void ensureDefaultStillExists(Long tenantId, AiParam current, Integer nextStatus) {
        if (current == null || current.getStatus() == null || current.getStatus() != StatusType.ENABLE.val) {
            return;
        }
        if (nextStatus != null && nextStatus == StatusType.ENABLE.val) {
            return;
        }
        long otherDefaultCount = this.count(new LambdaQueryWrapper<AiParam>()
                .eq(AiParam::getTenantId, tenantId)
                .eq(AiParam::getDeleted, 0)
                .eq(AiParam::getStatus, StatusType.ENABLE.val)
                .ne(AiParam::getId, current.getId()));
        if (otherDefaultCount == 0) {
            throw new CoolException("请先将其他 AI 参数设置为默认,再取消当前默认");
        }
    }
    private void ensureRemovingDefaultIsSafe(List<AiParam> records) {
        if (records == null || records.isEmpty()) {
            return;
        }
        records.stream()
                .filter(item -> item.getTenantId() != null && item.getStatus() != null && item.getStatus() == StatusType.ENABLE.val)
                .map(AiParam::getTenantId)
                .distinct()
                .forEach(this::ensureTenantHasRemainingDefaultAfterRemove);
    }
    private void ensureTenantHasRemainingDefaultAfterRemove(Long tenantId) {
        long defaultCount = this.count(new LambdaQueryWrapper<AiParam>()
                .eq(AiParam::getTenantId, tenantId)
                .eq(AiParam::getDeleted, 0)
                .eq(AiParam::getStatus, StatusType.ENABLE.val));
        if (defaultCount <= 1) {
            throw new CoolException("默认 AI 参数不能直接删除,请先将其他配置设为默认");
        }
    }
@@ -165,4 +315,14 @@
            throw new CoolException("解析校验时间失败: " + e.getMessage());
        }
    }
    private AiChatModelOptionDto toChatModelOption(AiParam aiParam) {
        return AiChatModelOptionDto.builder()
                .aiParamId(aiParam.getId())
                .name(aiParam.getName())
                .model(aiParam.getModel())
                .providerType(aiParam.getProviderType())
                .active(aiParam.getStatus() != null && aiParam.getStatus() == StatusType.ENABLE.val)
                .build();
    }
}