| | |
| | | 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) { |
| | |
| | | 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 |
| | |
| | | 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); |
| | | } |
| | |
| | | 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) { |
| | |
| | | } |
| | | 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 参数不能直接删除,请先将其他配置设为默认"); |
| | | } |
| | | } |
| | | |
| | |
| | | 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(); |
| | | } |
| | | } |