|
|
@ -1,10 +1,13 @@ |
|
|
package com.project.interaction.domain.service.impl; |
|
|
package com.project.interaction.domain.service.impl; |
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.lang.UUID; |
|
|
import cn.hutool.core.util.RandomUtil; |
|
|
import cn.hutool.core.util.RandomUtil; |
|
|
import com.project.information.domain.entity.KnowledgePointEntity; |
|
|
import com.project.information.domain.entity.KnowledgePointEntity; |
|
|
import com.project.information.domain.service.KnowledgePointBaseService; |
|
|
import com.project.information.domain.service.KnowledgePointBaseService; |
|
|
|
|
|
import com.project.interaction.domain.dto.GenerateQuestionQueueDTO; |
|
|
import com.project.interaction.domain.dto.GenerateQuestionRequestDTO; |
|
|
import com.project.interaction.domain.dto.GenerateQuestionRequestDTO; |
|
|
import com.project.interaction.domain.dto.GenerateQuestionResponseDTO; |
|
|
import com.project.interaction.domain.dto.GenerateQuestionResponseDTO; |
|
|
|
|
|
import com.project.interaction.domain.service.GenerateQuestionQueueService; |
|
|
import com.project.interaction.domain.service.PostToGenerateQuestionDomainService; |
|
|
import com.project.interaction.domain.service.PostToGenerateQuestionDomainService; |
|
|
import com.project.interaction.utils.NotifyUtil; |
|
|
import com.project.interaction.utils.NotifyUtil; |
|
|
import com.project.question.domain.dto.QuestionDTO; |
|
|
import com.project.question.domain.dto.QuestionDTO; |
|
|
@ -19,8 +22,12 @@ import org.springframework.beans.factory.annotation.Autowired; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.web.reactive.function.client.WebClient; |
|
|
import org.springframework.web.reactive.function.client.WebClient; |
|
|
|
|
|
import org.springframework.web.reactive.function.client.WebClientResponseException; |
|
|
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
import java.time.Duration; |
|
|
import java.time.Duration; |
|
|
|
|
|
import java.time.LocalDateTime; |
|
|
import java.util.*; |
|
|
import java.util.*; |
|
|
import java.util.stream.Collectors; |
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
@ -46,6 +53,9 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
@Autowired |
|
|
@Autowired |
|
|
private KnowledgePointBaseService knowledgePointBaseService; |
|
|
private KnowledgePointBaseService knowledgePointBaseService; |
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
private GenerateQuestionQueueService generateQuestionQueueService; |
|
|
|
|
|
|
|
|
@Autowired |
|
|
@Autowired |
|
|
private NotifyUtil notifyUtil; |
|
|
private NotifyUtil notifyUtil; |
|
|
|
|
|
|
|
|
@ -55,6 +65,10 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
|
|
|
|
|
|
private final List<Integer> list = Arrays.asList(0, 1, 2, 3); |
|
|
private final List<Integer> list = Arrays.asList(0, 1, 2, 3); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 算法服务拒绝状态码 |
|
|
|
|
|
*/ |
|
|
|
|
|
private static final Integer REJECT_STATUS = 429; |
|
|
|
|
|
|
|
|
@Value("${question.generation.downgrade:true}") |
|
|
@Value("${question.generation.downgrade:true}") |
|
|
private Boolean downgrade; |
|
|
private Boolean downgrade; |
|
|
@ -62,7 +76,8 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
/** |
|
|
/** |
|
|
* 生成题目 |
|
|
* 生成题目 |
|
|
* 1. 先尝试调用算法服务生成真实题目 |
|
|
* 1. 先尝试调用算法服务生成真实题目 |
|
|
* 2. 如果算法服务失败,使用熔断降级方案生成默认题目 |
|
|
* 2. 如果算法服务被拒绝,将请求加入队列,等待定时任务重试 |
|
|
|
|
|
* 3. 如果算法服务失败,使用熔断降级方案生成默认题目 |
|
|
*/ |
|
|
*/ |
|
|
@Override |
|
|
@Override |
|
|
public void postToGenerateQuestion(List<TaskKnowledgePointDTO> dtoList, QuestionTypeEnum questionType, int num) throws Exception { |
|
|
public void postToGenerateQuestion(List<TaskKnowledgePointDTO> dtoList, QuestionTypeEnum questionType, int num) throws Exception { |
|
|
@ -100,13 +115,29 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
}catch (Exception e) { |
|
|
log.warn(">>> [题目生成] 调用算法服务失败,启动熔断降级方案, TaskId: {}, 错误: {}", |
|
|
if (e instanceof WebClientResponseException exception) { |
|
|
taskId, e.getMessage()); |
|
|
int statusCode = exception.getStatusCode().value(); |
|
|
|
|
|
|
|
|
|
|
|
if (statusCode == REJECT_STATUS) { |
|
|
|
|
|
log.warn(">>> [题目生成] 算法服务拒绝请求, TaskId: {}, 状态: {}, 信息: {}, 将加入队列等待重试", |
|
|
|
|
|
taskId, statusCode, e.getMessage()); |
|
|
|
|
|
GenerateQuestionQueueDTO generateQuestionQueueItem = new GenerateQuestionQueueDTO(); |
|
|
|
|
|
generateQuestionQueueItem.setQuestionType(questionType); |
|
|
|
|
|
generateQuestionQueueItem.setNum(num); |
|
|
|
|
|
generateQuestionQueueItem.setDtoList(dtoList); |
|
|
|
|
|
generateQuestionQueueItem.setTaskId(dtoList.get(0).getTaskId()); |
|
|
|
|
|
generateQuestionQueueService.addItem(generateQuestionQueueItem); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.warn(">>> [题目生成] 调用算法服务失败, TaskId: {}", |
|
|
|
|
|
taskId); |
|
|
|
|
|
|
|
|
//发送预警通知
|
|
|
//消息提醒
|
|
|
notifyUtil.notify(taskId,clusterId); |
|
|
// notifyUtil.notify(taskId, clusterId);
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -137,6 +168,130 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
return generateQuestions(requestDTO); |
|
|
return generateQuestions(requestDTO); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private GenerateQuestionResponseDTO generateQuestions(GenerateQuestionRequestDTO requestDTO) { |
|
|
|
|
|
try { |
|
|
|
|
|
log.info(">>> [题目生成] 向算法服务发送请求, TaskId: {}, ClusterId: {}, 生成数量: {}", |
|
|
|
|
|
requestDTO.getCluster().getTaskId(), |
|
|
|
|
|
requestDTO.getCluster().getClusterId(), |
|
|
|
|
|
requestDTO.getNumQuestions()); |
|
|
|
|
|
|
|
|
|
|
|
// 发送请求到算法服务
|
|
|
|
|
|
GenerateQuestionResponseDTO response = algorithmWebClient.post() |
|
|
|
|
|
.uri(apiUrl+generateQuestionUrl) |
|
|
|
|
|
.bodyValue(requestDTO) |
|
|
|
|
|
.retrieve() |
|
|
|
|
|
.onStatus( |
|
|
|
|
|
status -> !status.is2xxSuccessful(), |
|
|
|
|
|
resp -> resp.bodyToMono(String.class) |
|
|
|
|
|
.flatMap(body -> { |
|
|
|
|
|
return Mono.error(WebClientResponseException.create( |
|
|
|
|
|
resp.statusCode().value(), |
|
|
|
|
|
"", |
|
|
|
|
|
resp.headers().asHttpHeaders(), |
|
|
|
|
|
body.getBytes(), |
|
|
|
|
|
StandardCharsets.UTF_8 |
|
|
|
|
|
)); |
|
|
|
|
|
}) |
|
|
|
|
|
) |
|
|
|
|
|
.bodyToMono(GenerateQuestionResponseDTO.class) |
|
|
|
|
|
.timeout(Duration.ofSeconds(30)) // 30秒超时
|
|
|
|
|
|
.retry(2) // 失败重试2次
|
|
|
|
|
|
.block(); // 同步等待结果
|
|
|
|
|
|
|
|
|
|
|
|
log.info(">>> [题目生成] 算法服务成功接收任务, TaskId: {}, AlgoTaskId: {}, 状态: {}", |
|
|
|
|
|
requestDTO.getCluster().getTaskId(), |
|
|
|
|
|
response != null ? response.getTaskId() : "null", |
|
|
|
|
|
response != null ? response.getStatus() : "null"); |
|
|
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
throw e; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 处理队列中的待重试项 |
|
|
|
|
|
* 由定时任务调用,一般间隔1分钟调用一次 |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public void processQueuedItems() { |
|
|
|
|
|
// 获取待重试的队列项
|
|
|
|
|
|
List<GenerateQuestionQueueDTO> retryItems = generateQuestionQueueService.getRetryItems(); |
|
|
|
|
|
|
|
|
|
|
|
if (retryItems.isEmpty()) { |
|
|
|
|
|
log.debug(">>> [队列管理] 当前没有待重试的项"); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.info(">>> [队列管理] 获取到 {} 个待重试的项", retryItems.size()); |
|
|
|
|
|
|
|
|
|
|
|
for (GenerateQuestionQueueDTO item : retryItems) { |
|
|
|
|
|
boolean shouldRequeue = retryGenerateQuestion(item); |
|
|
|
|
|
if (shouldRequeue) { |
|
|
|
|
|
generateQuestionQueueService.requeue(item); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.info(">>> [队列管理] 本次队列处理完成"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 重试生成题目 |
|
|
|
|
|
* @return true 如果需要重新入队等待下次重试,false 如果处理完成(成功或达到最大重试次数) |
|
|
|
|
|
*/ |
|
|
|
|
|
private boolean retryGenerateQuestion(GenerateQuestionQueueDTO item){ |
|
|
|
|
|
log.info(">>> [队列管理] 开始重试题目生成, 重试次数: {},ItemId: {}, TaskId: {}", |
|
|
|
|
|
item.getRetryCount() + 1, item.getItemId(),item.getTaskId()); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
long waitStart = System.currentTimeMillis(); |
|
|
|
|
|
rateLimiter.acquire(); |
|
|
|
|
|
long waitTime = System.currentTimeMillis() - waitStart; |
|
|
|
|
|
if (waitTime > 100) { |
|
|
|
|
|
log.info(">>> [队列管理] 重试因限流等待了 {} ms, TaskID: {}", waitTime, item.getTaskId()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
KnowledgePointEntity knowledgePoint = knowledgePointBaseService.getById( |
|
|
|
|
|
item.getDtoList().get(0).getAtomId()); |
|
|
|
|
|
|
|
|
|
|
|
GenerateQuestionResponseDTO response = callAlgorithmService( |
|
|
|
|
|
item.getDtoList(), |
|
|
|
|
|
item.getQuestionType(), |
|
|
|
|
|
item.getNum(), |
|
|
|
|
|
knowledgePoint.getParseName()); |
|
|
|
|
|
|
|
|
|
|
|
if (response != null && StringUtils.isNotBlank(response.getMessage()) && |
|
|
|
|
|
response.getMessage().contains("success")) { |
|
|
|
|
|
log.info(">>> [队列管理] 重试成功!ItemId: {}, TaskId: {}", item.getItemId(), |
|
|
|
|
|
item.getTaskId()); |
|
|
|
|
|
generateQuestionQueueService.removeItem(item.getItemId()); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
if (e instanceof WebClientResponseException exception) { |
|
|
|
|
|
int statusCode = exception.getStatusCode().value(); |
|
|
|
|
|
|
|
|
|
|
|
if (statusCode == REJECT_STATUS) { |
|
|
|
|
|
log.warn(">>> [队列管理] 重试仍被拒绝, ItemId: {}", |
|
|
|
|
|
item.getItemId(), exception.getMessage()); |
|
|
|
|
|
item.setLastRetryTime(LocalDateTime.now()); |
|
|
|
|
|
item.setRetryCount(item.getRetryCount() + 1); |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
log.error(">>> [队列管理] 重试生成题目异常, ItemId: {},TaskId: {}", |
|
|
|
|
|
item.getItemId(),item.getTaskId()); |
|
|
|
|
|
item.setLastRetryTime(LocalDateTime.now()); |
|
|
|
|
|
item.setRetryCount(item.getRetryCount() + 1); |
|
|
|
|
|
|
|
|
|
|
|
notifyUtil.notify(item.getTaskId(), item.getDtoList().get(0).getClusterId()); |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 熔断降级:生成硬编码的默认题目当算法服务失败时使用 |
|
|
* 熔断降级:生成硬编码的默认题目当算法服务失败时使用 |
|
|
*/ |
|
|
*/ |
|
|
@ -195,45 +350,6 @@ public class PostToGenerateQuestionDomainServiceImpl implements PostToGenerateQu |
|
|
saveQuestionDomainService.save(questionDTO); |
|
|
saveQuestionDomainService.save(questionDTO); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private GenerateQuestionResponseDTO generateQuestions(GenerateQuestionRequestDTO requestDTO) { |
|
|
|
|
|
try { |
|
|
|
|
|
log.info(">>> [题目生成] 向算法服务发送请求, TaskId: {}, ClusterId: {}, 生成数量: {}", |
|
|
|
|
|
requestDTO.getCluster().getTaskId(), |
|
|
|
|
|
requestDTO.getCluster().getClusterId(), |
|
|
|
|
|
requestDTO.getNumQuestions()); |
|
|
|
|
|
|
|
|
|
|
|
// 发送请求到算法服务
|
|
|
|
|
|
GenerateQuestionResponseDTO response = algorithmWebClient.post() |
|
|
|
|
|
.uri(apiUrl+generateQuestionUrl) |
|
|
|
|
|
.bodyValue(requestDTO) |
|
|
|
|
|
.retrieve() |
|
|
|
|
|
.onStatus( |
|
|
|
|
|
status -> !status.is2xxSuccessful(), |
|
|
|
|
|
resp -> resp.bodyToMono(String.class) |
|
|
|
|
|
.flatMap(body -> reactor.core.publisher.Mono.error( |
|
|
|
|
|
new RuntimeException("算法服务返回错误: " + body) |
|
|
|
|
|
)) |
|
|
|
|
|
) |
|
|
|
|
|
.bodyToMono(GenerateQuestionResponseDTO.class) |
|
|
|
|
|
.timeout(Duration.ofSeconds(30)) // 30秒超时
|
|
|
|
|
|
.retry(2) // 失败重试2次
|
|
|
|
|
|
.block(); // 同步等待结果
|
|
|
|
|
|
|
|
|
|
|
|
log.info(">>> [题目生成] 算法服务成功接收任务, TaskId: {}, AlgoTaskId: {}, 状态: {}", |
|
|
|
|
|
requestDTO.getCluster().getTaskId(), |
|
|
|
|
|
response != null ? response.getTaskId() : "null", |
|
|
|
|
|
response != null ? response.getStatus() : "null"); |
|
|
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error(">>> [题目生成] 调用算法服务异常, TaskId: {}, 错误: {}", |
|
|
|
|
|
requestDTO.getCluster().getTaskId(), |
|
|
|
|
|
e.getMessage(), e); |
|
|
|
|
|
throw new RuntimeException("调用算法服务生成题目失败: " + e.getMessage(), e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|