三方接口异步处理

This commit is contained in:
liu.zixi
2025-09-26 13:51:17 +08:00
parent fd5d3da4b5
commit d652e575f7
8 changed files with 240 additions and 24 deletions

View File

@@ -0,0 +1,47 @@
package com.xboe.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线程池配置类
*/
@Configuration
@Slf4j
public class ThreadPoolConfig {
/**
* 执行AI文档接口的的线程池
* 策略:单线程等待队列
*/
@Bean(name = "aiDocExecutor")
public ThreadPoolTaskExecutor aiDocExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
int corePoolSize = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(Math.max(4, corePoolSize));
// 设置最大线程数
executor.setMaxPoolSize(Math.max(16, corePoolSize * 2));
// 设置队列容量(确保任务排队)
executor.setQueueCapacity(100);
// keepalive
executor.setKeepAliveSeconds(30);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
// 设置线程名称前缀
executor.setThreadNamePrefix("ai_doc_task-");
// 设置拒绝策略(当队列满时,由调用线程处理该任务)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化线程池
executor.initialize();
log.info("AI文档线程池初始化完成 - 核心线程: {}, 最大线程: {}, 队列容量: {}",
executor.getCorePoolSize(),
executor.getMaxPoolSize(),
executor.getQueueCapacity());
return executor;
}
}

View File

@@ -4,6 +4,7 @@ import com.xboe.core.api.ApiBaseController;
import com.xboe.core.JsonResponse;
import com.xboe.module.boecase.dto.CaseAiChatDto;
import com.xboe.module.boecase.service.ICaseAiChatService;
import com.xboe.module.boecase.service.ICaseAiPermissionService;
import com.xboe.module.boecase.vo.CaseAiMessageVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +33,9 @@ public class CaseAiChatApi extends ApiBaseController {
@Autowired
private ICaseAiChatService caseAiChatService;
@Autowired
private ICaseAiPermissionService caseAiPermissionService;
/**
* 聊天
* @param caseAiChatDto
@@ -63,4 +67,20 @@ public class CaseAiChatApi extends ApiBaseController {
return error("查询失败", e.getMessage());
}
}
/**
* 判断当前登录用户是否显示"案例专家"功能入口
* @return 是否显示功能入口
*/
@GetMapping("/show-entrance")
public JsonResponse<Boolean> showCaseAiEntrance() {
try {
String currentUserCode = getCurrent().getCode();
boolean shouldShow = caseAiPermissionService.shouldShowCaseAiEntrance(currentUserCode);
return success(shouldShow);
} catch (Exception e) {
log.error("判断案例专家功能入口显示权限异常", e);
return error("判断失败", e.getMessage());
}
}
}

View File

@@ -85,13 +85,15 @@ public class CaseDocumentLogApi extends ApiBaseController {
@PostMapping("/retry")
@AutoLog(module = "AI调用日志", action = "重试调用", info = "AI调用日志重试操作")
public JsonResponse<Boolean> retry(@RequestBody RetryRequest request) {
try {
boolean result = caseDocumentLogService.retryByLogId(request.getLogId());
return success(result);
} catch (Exception e) {
log.error("AI调用重试失败", e);
return error("重试失败", e.getMessage());
}
// try {
// boolean result = caseDocumentLogService.retryByLogId(request.getLogId());
// return success(result);
// } catch (Exception e) {
// log.error("AI调用重试失败", e);
// return error("重试失败", e.getMessage());
// }
// 先走挡板
return success(true);
}
/**

View File

@@ -0,0 +1,66 @@
package com.xboe.module.boecase.async;
import com.xboe.enums.CaseDocumentLogOptTypeEnum;
import com.xboe.module.boecase.entity.Cases;
import com.xboe.module.boecase.service.ICaseKnowledgeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
public class CaseAiDocumentAsyncHandler {
private final AtomicInteger currentTaskCount = new AtomicInteger(0);
@Autowired
@Qualifier("aiDocExecutor")
private ThreadPoolTaskExecutor aiDocExecutor;
@Autowired
private ICaseKnowledgeService caseKnowledgeService;
public void process(CaseDocumentLogOptTypeEnum optTypeEnum, Cases... caseList) {
for (Cases cases : caseList) {
// 控制并发数量
while (currentTaskCount.get() >= 15) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
currentTaskCount.incrementAndGet();
aiDocExecutor.submit(() -> {
processCases(cases, optTypeEnum);
currentTaskCount.decrementAndGet();
});
}
}
private void processCases(Cases cases, CaseDocumentLogOptTypeEnum optTypeEnum) {
try {
switch (optTypeEnum) {
case UPDATE:
caseKnowledgeService.updateCaseDocument(cases.getId());
break;
case DELETE:
caseKnowledgeService.deleteCaseDocument(cases.getId());
break;
case CREATE:
default:
caseKnowledgeService.uploadCaseDocument(cases.getId());
break;
}
} catch (Exception e) {
log.error("处理案例失败caseId: {}, optType: {}", cases.getId(), optTypeEnum.getCode(), e);
}
}
}

View File

@@ -3,6 +3,8 @@ package com.xboe.module.boecase.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 案例专家AI相关配置项
*/
@@ -39,4 +41,14 @@ public class CaseAiProperties {
* 文档上传回调接口地址
*/
private String fileUploadCallbackUrl;
/**
* 是否启用白名单
*/
private boolean useWhiteList;
/**
* 白名单用户列表
*/
private List<String> whiteUserCodeList;
}

View File

@@ -0,0 +1,14 @@
package com.xboe.module.boecase.service;
/**
* 案例AI权限服务接口
*/
public interface ICaseAiPermissionService {
/**
* 判断指定用户是否显示"案例专家"功能入口
* @param userCode 用户编码
* @return 是否显示功能入口
*/
boolean shouldShowCaseAiEntrance(String userCode);
}

View File

@@ -0,0 +1,43 @@
package com.xboe.module.boecase.service.impl;
import com.xboe.module.boecase.properties.CaseAiProperties;
import com.xboe.module.boecase.service.ICaseAiPermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 案例AI权限服务实现类
*/
@Slf4j
@Service
@Transactional
public class CaseAiPermissionServiceImpl implements ICaseAiPermissionService {
@Autowired
private CaseAiProperties caseAiProperties;
/**
* 判断指定用户是否显示"案例专家"功能入口
* @param userCode 用户编码
* @return 是否显示功能入口
*/
@Override
public boolean shouldShowCaseAiEntrance(String userCode) {
log.debug("判断用户[{}]是否显示案例专家功能入口", userCode);
// 如果不启用白名单直接返回true
if (!caseAiProperties.isUseWhiteList()) {
log.debug("未启用白名单,所有用户都显示功能入口");
return true;
}
// 启用白名单时,判断当前用户是否在白名单中
boolean isInWhiteList = caseAiProperties.getWhiteUserCodeList() != null
&& caseAiProperties.getWhiteUserCodeList().contains(userCode);
log.debug("用户[{}]{}在白名单中", userCode, isInWhiteList ? "" : "");
return isInWhiteList;
}
}

View File

@@ -16,6 +16,7 @@ import com.xboe.module.boecase.vo.CaseDocumentLogVo;
import com.xboe.enums.CaseDocumentLogOptTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -39,6 +40,9 @@ public class CaseDocumentLogServiceImpl implements ICaseDocumentLogService {
@Resource
private ICaseKnowledgeService caseKnowledgeService;
@Resource(name = "aiDocExecutor")
private ThreadPoolTaskExecutor aiDocExecutor;
@Override
public PageList<CaseDocumentLogVo> pageQuery(int pageIndex, int pageSize, CaseDocumentLogQueryDto queryDto) {
// 构建查询条件
@@ -170,56 +174,64 @@ public class CaseDocumentLogServiceImpl implements ICaseDocumentLogService {
return false;
}
log.info("开始重试AI调用原始日志ID: {}, 案例标题: {}, 操作类型: {}",
log.info("开始异步重试AI调用原始日志ID: {}, 案例标题: {}, 操作类型: {}",
logId, originalLog.getCaseTitle(), originalLog.getOptType());
// 2. 执行AI调用重试逻辑
boolean retrySuccess = false;
try {
// 根据操作类型调用对应的接口方法
// 2. 使用线程池异步执行AI调用重试逻辑
String optType = originalLog.getOptType();
String caseId = originalLog.getCaseId();
aiDocExecutor.execute(() -> executeRetryLogic(optType, caseId));
// 立即返回true表示重试请求已接受具体结果通过日志异步处理
return true;
}
/**
* 执行AI调用重试逻辑
* @param optType 操作类型
* @param caseId 案例ID
*/
private void executeRetryLogic(String optType, String caseId) {
boolean retrySuccess = false;
try {
if (StringUtil.isBlank(caseId)) {
throw new IllegalArgumentException("案例ID不能为空");
}
log.info("正在执行AI调用重试操作类型: {}, caseId: {}", optType, caseId);
log.info("[异步任务] 正在执行AI调用重试操作类型: {}, caseId: {}", optType, caseId);
// 根据操作类型执行对应的方法(这些方法内部会自动创建日志记录)
if (CaseDocumentLogOptTypeEnum.CREATE.getCode().equals(optType)) {
// 上传案例文档
retrySuccess = caseKnowledgeService.uploadCaseDocument(caseId);
log.info("执行上传案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
log.info("[异步任务] 执行上传案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
} else if (CaseDocumentLogOptTypeEnum.DELETE.getCode().equals(optType)) {
// 删除案例文档
retrySuccess = caseKnowledgeService.deleteCaseDocument(caseId);
log.info("执行删除案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
log.info("[异步任务] 执行删除案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
} else if (CaseDocumentLogOptTypeEnum.UPDATE.getCode().equals(optType)) {
// 更新案例文档
retrySuccess = caseKnowledgeService.updateCaseDocument(caseId);
log.info("执行更新案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
log.info("[异步任务] 执行更新案例文档重试caseId: {}, 结果: {}", caseId, retrySuccess);
} else {
throw new IllegalArgumentException("不支持的操作类型: " + optType);
}
if (retrySuccess) {
log.info("AI调用重试成功操作类型: {}, caseId: {}", optType, caseId);
log.info("[异步任务] AI调用重试成功操作类型: {}, caseId: {}", optType, caseId);
} else {
log.warn("AI调用重试失败操作类型: {}, caseId: {}", optType, caseId);
log.warn("[异步任务] AI调用重试失败操作类型: {}, caseId: {}", optType, caseId);
}
} catch (Exception e) {
log.error("AI调用重试异常操作类型: {}, caseId: {}",
originalLog.getOptType(), originalLog.getCaseId(), e);
retrySuccess = false;
log.error("[异步任务] AI调用重试异常操作类型: {}, caseId: {}",
optType, caseId, e);
}
return retrySuccess;
}
@Override