AI模型多阶段调用进度追踪系统设计文档
AI模型多阶段调用进度追踪系统设计文档
一、系统概述
为解决AI模型处理大型文件时响应时间长的问题,我们设计并实现了一套异步进度追踪系统。该系统采用Server-Sent Events (SSE) 技术,建立从服务器到客户端的单向实时通信通道,使前端能够实时获取后端文件处理的进度信息。
二、核心功能
- 异步文件处理:通过异步方式处理上传的文件,避免阻塞HTTP请求线程
- 实时进度推送:使用SSE技术向前端实时推送处理进度
- 多阶段处理跟踪:精确追踪每个AI处理阶段的进度
- 错误处理与恢复:完善的异常处理机制,确保资源得到释放
三、系统架构
系统架构由以下几个关键组件组成:
- 进度追踪服务:核心服务,管理任务进度和SSE连接
- 进度追踪控制器:提供REST API,供前端获取进度信息
- 文件处理服务增强:在现有服务上增加进度报告功能
- 文件上传控制器适配:修改现有控制器适配异步处理
四、后端实现代码
1. 进度追踪服务接口(IProgressTrackingService.java)
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;/*** 进度追踪服务接口* 用于追踪长时间运行任务的进度并推送给前端*/
public interface IProgressTrackingService {/*** 创建新的进度追踪会话** @param taskId 任务ID* @return SseEmitter 事件发射器*/SseEmitter createProgressTracker(String taskId);/*** 更新任务进度** @param taskId 任务ID* @param currentStage 当前阶段索引(从0开始)* @param totalStages 总阶段数* @param message 进度消息*/void updateProgress(String taskId, int currentStage, int totalStages, String message);/*** 完成任务进度追踪** @param taskId 任务ID* @param success 是否成功* @param message 完成消息*/void completeProgress(String taskId, boolean success, String message);
}
2. 进度追踪服务实现(ProgressTrackingServiceImpl.java)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.greatech.abnormal_monitoring.service.IProgressTrackingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 进度追踪服务实现类* 用于追踪长时间运行任务的进度并推送给前端*/
@Slf4j
@Service
public class ProgressTrackingServiceImpl implements IProgressTrackingService {// 存储任务ID到SSE发射器的映射private final Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();// JSON对象映射器private final ObjectMapper objectMapper = new ObjectMapper();/*** 创建新的进度追踪会话** @param taskId 任务ID* @return SseEmitter 事件发射器*/@Overridepublic SseEmitter createProgressTracker(String taskId) {// 创建一个超时时间为30分钟的SSE发射器SseEmitter emitter = new SseEmitter(1800000L);// 设置完成回调emitter.onCompletion(() -> {log.info("进度追踪会话已完成: {}", taskId);emitterMap.remove(taskId);});// 设置超时回调emitter.onTimeout(() -> {log.warn("进度追踪会话超时: {}", taskId);emitterMap.remove(taskId);});// 设置错误回调emitter.onError(ex -> {log.error("进度追踪会话发生错误: {}", taskId, ex);emitterMap.remove(taskId);});// 将发射器存储到映射中emitterMap.put(taskId, emitter);try {// 发送初始化事件Map<String, Object> initialEvent = Map.of("type", "INIT","taskId", taskId,"message", "进度追踪会话已创建","progress", 0);emitter.send(SseEmitter.event().data(objectMapper.writeValueAsString(initialEvent)));} catch (IOException e) {log.error("发送初始化事件失败: {}", taskId, e);emitter.completeWithError(e);}return emitter;}/*** 更新任务进度** @param taskId 任务ID* @param currentStage 当前阶段索引(从0开始)* @param totalStages 总阶段数* @param message 进度消息*/@Overridepublic void updateProgress(String taskId, int currentStage, int totalStages, String message) {SseEmitter emitter = emitterMap.get(taskId);if (emitter == null) {log.warn("尝试更新不存在的进度追踪会话: {}", taskId);return;}try {// 计算进度百分比int progress = (int) Math.round((currentStage * 100.0) / totalStages);// 创建进度事件Map<String, Object> progressEvent = Map.of("type", "PROGRESS","taskId", taskId,"currentStage", currentStage,"totalStages", totalStages,"progress", progress,"message", message);// 发送进度事件emitter.send(SseEmitter.event().data(objectMapper.writeValueAsString(progressEvent)));log.debug("已更新进度: taskId={}, stage={}/{}, progress={}%, message={}", taskId, currentStage, totalStages, progress, message);} catch (IOException e) {log.error("发送进度更新失败: {}", taskId, e);emitter.completeWithError(e);}}/*** 完成任务进度追踪** @param taskId 任务ID* @param success 是否成功* @param message 完成消息*/@Overridepublic void completeProgress(String taskId, boolean success, String message) {SseEmitter emitter = emitterMap.get(taskId);if (emitter == null) {log.warn("尝试完成不存在的进度追踪会话: {}", taskId);return;}try {// 创建完成事件Map<String, Object> completeEvent = Map.of("type", success ? "COMPLETE" : "ERROR","taskId", taskId,"progress", success ? 100 : -1,"message", message,"success", success);// 发送完成事件emitter.send(SseEmitter.event().data(objectMapper.writeValueAsString(completeEvent)));log.info("进度追踪已完成: taskId={}, success={}, message={}", taskId, success, message);// 关闭SSE连接emitter.complete();} catch (IOException e) {log.error("发送完成事件失败: {}", taskId, e);emitter.completeWithError(e);} finally {// 确保从映射中移除emitterMap.remove(taskId);}}
}
3. 进度追踪控制器(ProgressTrackingController.java)
import com.greatech.abnormal_monitoring.service.IProgressTrackingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.UUID;/*** 进度追踪控制器* 提供创建和获取进度追踪会话的接口*/
@Slf4j
@RestController
@RequestMapping("/api/progress")
@RequiredArgsConstructor
@Tag(name = "进度追踪", description = "提供长时间运行任务的进度追踪功能")
public class ProgressTrackingController {private final IProgressTrackingService progressTrackingService;/*** 创建新的进度追踪会话** @return SseEmitter 事件发射器*/@Operation(summary = "创建进度追踪会话", description = "创建一个新的进度追踪会话并返回SSE连接")@GetMapping(value = "/track", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter createProgressTracker(@Parameter(description = "任务ID(可选)") @RequestParam(required = false) String taskId) {// 如果未提供任务ID,则生成一个新IDif (taskId == null || taskId.isEmpty()) {taskId = UUID.randomUUID().toString();}log.info("创建进度追踪会话: {}", taskId);return progressTrackingService.createProgressTracker(taskId);}/*** 获取特定任务的进度追踪会话** @param taskId 任务ID* @return SseEmitter 事件发射器*/@Operation(summary = "获取任务进度", description = "根据任务ID获取进度追踪会话并返回SSE连接")@GetMapping(value = "/track/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter getProgressTracker(@Parameter(description = "任务ID", required = true) @PathVariable String taskId) {log.info("获取进度追踪会话: {}", taskId);return progressTrackingService.createProgressTracker(taskId);}
}
4. 修改后的文件上传控制器方法
/*** 上传模板文件并解析数据** @param file 模板文件* @param templateId 模板ID* @return 上传结果*/
@Operation(summary = "上传模板文件并解析", description = "上传模板文件到服务器并解析其内容到数据库")
@PostMapping("/upload-template")
public ServerResponse<Map<String, Object>> uploadAndParseTemplate(@Parameter(description = "模板文件", required = true) @RequestParam("file") MultipartFile file,@RequestParam("templateId") Long templateId) {log.info("开始处理模板文件上传请求,模板ID: {}, 文件名: {}", templateId, file.getOriginalFilename());try {// 处理文件并获取结果(现在包含任务ID)String result = templateService.processTemplateFile(file, templateId);// 从结果中提取任务IDString taskId = "";if (result.contains("任务ID:")) {taskId = result.substring(result.indexOf("任务ID:") + 5).trim();} else {// 对于不支持进度追踪的文件处理(如Excel文件),直接返回处理结果log.info("模板文件处理成功,模板ID: {}, 文件名: {}", templateId, file.getOriginalFilename());Map<String, Object> directResult = new HashMap<>();directResult.put("message", result);directResult.put("completed", true);return ServerResponse.success(directResult);}log.info("模板文件上传成功并开始异步处理,模板ID: {}, 文件名: {}, 任务ID: {}", templateId, file.getOriginalFilename(), taskId);// 构建响应数据Map<String, Object> responseData = new HashMap<>();responseData.put("taskId", taskId);responseData.put("message", "文件上传成功,正在异步处理中");responseData.put("completed", false);return ServerResponse.success(responseData);} catch (Exception e) {log.error("模板文件处理失败,模板ID: {}, 文件名: {}", templateId, file.getOriginalFilename(), e);return ServerResponse.error("文件处理失败: " + e.getMessage());}
}
5. 模板服务处理文本文件方法(TemplateServiceImpl.java中的processTextFile方法)
/*** 处理文本文件** @param file 已保存的文件* @param templateId 模板ID* @return 处理结果*/
public String processTextFile(File file, Long templateId) {log.info("处理文本文件:{}, 使用会话方式调用大模型API", file.getName());// 生成唯一任务ID用于进度追踪String taskId = UUID.randomUUID().toString();log.info("生成进度追踪任务ID: {}", taskId);// 创建新的会话IDString sessionId = aiCommonService.createNewSession();log.info("创建新会话:{}", sessionId);// 使用CompletableFuture异步处理CompletableFuture.runAsync(() -> {try {// 将文件转换为MultipartFile以便上传MultipartFile multipartFile = convertToMultipartFile(file);// 读取提示词模板并按阶段拆分String promptTemplateContent = readPromptTemplate();List<String> promptStages = splitContentIntoStages(promptTemplateContent);log.info("提示词模板已拆分为{}个阶段", promptStages.size());// 初始化进度progressTrackingService.updateProgress(taskId, 0, promptStages.size(), "开始处理文本文件,共" + promptStages.size() + "个阶段");String jsonResponse = null;// 顺序执行每个阶段,只保留最后一个阶段的结果for (int i = 0; i < promptStages.size(); i++) {String stagePrompt = promptStages.get(i);// 更新进度progressTrackingService.updateProgress(taskId, i, promptStages.size(), "正在执行阶段 " + (i+1) + "/" + promptStages.size());log.info("执行阶段 {}/{}", i+1, promptStages.size());// 对于第一个阶段,需要上传文件if (i == 0) {jsonResponse = aiCommonService.callAiModelWithFileContext(stagePrompt, sessionId, multipartFile);} else {// 后续阶段只需要发送提示词jsonResponse = aiCommonService.callAiModelWithFileContext(stagePrompt, sessionId, null);}// 仅记录当前处于哪个阶段,不记录完整响应以避免日志过大log.info("阶段 {}/{} 执行完成", i+1, promptStages.size());// 更新进度progressTrackingService.updateProgress(taskId, i+1, promptStages.size(), "已完成阶段 " + (i+1) + "/" + promptStages.size());}log.info("所有阶段执行完成,获取最终JSON响应");progressTrackingService.updateProgress(taskId, promptStages.size(), promptStages.size(), "所有阶段执行完成,正在处理最终结果");// 将JSON响应转换为Excel文件String excelFilePath = convertJsonToExcelFile(jsonResponse, templateId);// 使用Excel解析逻辑处理生成的Excel文件sheetService.parseExcelFile(new File(excelFilePath), templateId);// 清理会话资源aiCommonService.clearSession(sessionId);log.info("会话已清理:{}", sessionId);// 标记任务完成progressTrackingService.completeProgress(taskId, true, "文本文件已成功处理并解析,原始文件路径:" + file.getAbsolutePath());} catch (Exception e) {// 出错时也要尝试清理会话资源try {aiCommonService.clearSession(sessionId);log.info("会话清理完成:{}", sessionId);} catch (Exception ex) {log.error("清理会话失败:{}", sessionId, ex);}log.error("处理文本文件失败", e);// 标记任务失败progressTrackingService.completeProgress(taskId, false, "处理文件失败: " + e.getMessage());}});// 立即返回,不阻塞请求线程return "文件处理已开始,可通过任务ID: " + taskId + " 追踪进度";
}
6. 提取阶段的灵活实现(splitContentIntoStages方法)
/*** 将文件内容按阶段拆分,使用配置文件中的设置** @param content 文件内容* @return 拆分后的阶段列表*/
private List<String> splitContentIntoStages(String content) {List<String> stages = new ArrayList<>();// 1. 首先尝试使用配置的标识符列表String identifiersConfig = environment.getProperty("template.stage.identifiers");if (identifiersConfig != null && !identifiersConfig.isEmpty()) {return splitByConfiguredIdentifiers(content, identifiersConfig);}// 2. 如果没有配置具体标识符,则使用正则表达式return splitByRegexPattern(content);
}/*** 根据配置的标识符列表拆分内容*/
private List<String> splitByConfiguredIdentifiers(String content, String identifiersConfig) {List<String> stages = new ArrayList<>();String[] identifiers = identifiersConfig.split(",");log.info("使用配置的 {} 个阶段标识符拆分内容", identifiers.length);// 寻找每个阶段的起始和结束位置int currentPos = 0;for (int i = 0; i < identifiers.length; i++) {String identifier = identifiers[i].trim();int stageStart = content.indexOf(identifier, currentPos);if (stageStart != -1) {// 找到阶段起始位置int nextStageStart = -1;// 查找下一个阶段的起始位置(如果存在)if (i < identifiers.length - 1) {nextStageStart = content.indexOf(identifiers[i + 1].trim(), stageStart);}// 提取当前阶段内容String stageContent;if (nextStageStart != -1) {stageContent = content.substring(stageStart, nextStageStart).trim();currentPos = nextStageStart;} else {// 如果是最后一个阶段,则截取到文件末尾stageContent = content.substring(stageStart).trim();}log.info("提取阶段 {}: {} 字符", i+1, stageContent.length());stages.add(stageContent);} else {log.warn("未找到阶段标识符: {}", identifier);}}return stages;
}/*** 使用正则表达式模式拆分内容*/
private List<String> splitByRegexPattern(String content) {List<String> stages = new ArrayList<>();// 获取配置的正则表达式,如果未配置则使用默认值String patternStr = environment.getProperty("template.stage.pattern");if (patternStr == null || patternStr.isEmpty()) {patternStr = "(?:#{0,3}\\s*【?\\s*阶段\\s*(\\d+)\\s*】?)";}log.info("使用正则表达式模式拆分内容: {}", patternStr);// 使用正则表达式匹配阶段标识符Pattern stagePattern = Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE);Matcher matcher = stagePattern.matcher(content);// 存储找到的所有阶段起始位置和编号Map<Integer, Integer> positionToStageNumber = new TreeMap<>();while (matcher.find()) {int stageNumber = Integer.parseInt(matcher.group(1));positionToStageNumber.put(matcher.start(), stageNumber);log.debug("发现阶段 {} 在位置 {}", stageNumber, matcher.start());}// 如果没有找到任何阶段标识,将整个内容作为一个阶段if (positionToStageNumber.isEmpty()) {log.warn("未在内容中找到阶段标识符,将整个内容作为单一阶段处理");stages.add(content);return stages;}// 是否严格按照阶段编号排序boolean strictOrder = Boolean.parseBoolean(environment.getProperty("template.stage.strict-order", "true"));// 获取最大阶段数限制int maxStageCount = Integer.parseInt(environment.getProperty("template.stage.max-count", "10"));// 提取每个阶段的内容List<Integer> positions = new ArrayList<>(positionToStageNumber.keySet());Collections.sort(positions);Map<Integer, String> numberToContent = new HashMap<>();for (int i = 0; i < positions.size(); i++) {int startPos = positions.get(i);int endPos = (i < positions.size() - 1) ? positions.get(i + 1) : content.length();String stageContent = content.substring(startPos, endPos).trim();int stageNumber = positionToStageNumber.get(startPos);if (stageNumber > maxStageCount) {log.warn("阶段编号 {} 超过最大限制 {},将被忽略", stageNumber, maxStageCount);continue;}log.info("提取阶段 {}: {} 字符", stageNumber, stageContent.length());numberToContent.put(stageNumber, stageContent);}// 按照阶段编号排序if (strictOrder) {List<Integer> sortedNumbers = new ArrayList<>(numberToContent.keySet());Collections.sort(sortedNumbers);for (Integer num : sortedNumbers) {stages.add(numberToContent.get(num));}} else {// 按照在文件中的位置顺序for (int i = 0; i < positions.size(); i++) {int stageNumber = positionToStageNumber.get(positions.get(i));if (stageNumber <= maxStageCount) {stages.add(numberToContent.get(stageNumber));}}}log.info("共拆分出 {} 个阶段", stages.size());return stages;
}
五、前端调用说明
这里提供简要的前端调用说明,以便于前端开发人员理解如何与后端集成。
1. 前端调用流程
前端与后端的交互流程如下:
- 文件上传:使用标准的HTTP POST请求上传文件到
/api/template/upload-template
- 获取任务ID:从上传响应中提取任务ID
- 建立SSE连接:使用
EventSource
接口建立与/api/progress/track/{taskId}
的连接 - 处理进度更新:监听并处理SSE事件,更新UI显示进度
- 处理完成通知:接收处理完成或失败的通知,更新UI状态
2. 代码示例
以下是前端实现的简化示例:
// 文件上传示例
async function uploadFile(file, templateId) {const formData = new FormData();formData.append('file', file);formData.append('templateId', templateId);try {const response = await fetch('/api/template/upload-template', {method: 'POST',body: formData});const result = await response.json();if (result.code === 200) {if (result.data.taskId) {// 异步处理任务,开始跟踪进度trackProgress(result.data.taskId);} else if (result.data.completed) {// 同步处理已完成showSuccess(result.data.message);}} else {showError(result.message);}} catch (error) {showError('上传失败: ' + error.message);}
}// 进度追踪示例
function trackProgress(taskId) {// 创建EventSource连接const eventSource = new EventSource(`/api/progress/track/${taskId}`);// 处理消息事件eventSource.onmessage = (event) => {const data = JSON.parse(event.data);switch (data.type) {case 'INIT':updateProgressUI(0, data.message);break;case 'PROGRESS':updateProgressUI(data.progress, data.message);break;case 'COMPLETE':updateProgressUI(100, data.message);showSuccess(data.message);eventSource.close();break;case 'ERROR':showError(data.message);eventSource.close();break;}};// 处理错误eventSource.onerror = (error) => {showError('连接中断,无法获取进度更新');eventSource.close();};
}// 更新UI显示进度(由前端实现)
function updateProgressUI(progress, message) {// 更新进度条document.getElementById('progress-bar').value = progress;document.getElementById('progress-percentage').textContent = progress + '%';document.getElementById('progress-message').textContent = message;
}// 显示成功消息(由前端实现)
function showSuccess(message) {// 显示成功提示
}// 显示错误消息(由前端实现)
function showError(message) {// 显示错误提示
}
在Spring Boot应用程序配置中启用异步支持:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(25);executor.setThreadNamePrefix("async-");executor.initialize();return executor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new SimpleAsyncUncaughtExceptionHandler();}
}
确保在应用程序上下文中注册以下组件:
- ProgressTrackingServiceImpl
- ProgressTrackingController
六、安全性考虑
- 任务ID安全:生成的任务ID应使用安全的随机数生成器,避免被猜测
- 资源限制:设置SSE连接和异步处理的超时时间
- 错误处理:确保异常不会泄露敏感信息
- 防止资源泄露:正确清理未使用的连接和资源
- 访问控制:根据需要为进度追踪API添加身份验证
七、性能优化
- 异步线程池调优:根据服务器性能和预期负载调整线程池参数
- 超时控制:为长时间运行的任务设置合理的超时时间
- 内存管理:避免在SSE消息中发送大量数据
- 数据压缩:考虑对SSE消息进行压缩,减少网络流量
八、故障排除
常见问题及解决方案:
SSE连接中断
- 原因:网络问题、服务器重启、超时
- 解决:前端实现自动重连逻辑
任务处理缓慢
- 原因:AI模型处理速度、服务器资源不足
- 解决:增加服务器资源、优化AI调用参数
任务进度更新失败
- 原因:SSE连接已关闭、进度追踪服务异常
- 解决:检查日志、确保任务ID正确
资源泄露
- 原因:未正确关闭SSE连接或清理资源
- 解决:确保所有异常路径都调用资源清理方法
九、扩展方向
系统可以在以下方向进行扩展:
- 任务持久化:将任务进度存储到数据库,支持服务重启后恢复
- 任务队列:使用消息队列管理大量任务,避免服务器过载
- 分布式部署:支持集群环境中的任务进度追踪
- 管理界面:添加管理界面,监控和管理所有正在运行的任务
- 推送通知:集成WebSocket或推送通知,提供更实时的进度更新
十、总结
本系统提供了一种高效、可靠的方式来追踪AI模型多阶段调用的处理进度。通过使用Server-Sent Events技术和异步处理,系统能够在长时间运行的任务中向前端实时推送进度更新,大大提高了用户体验。该设计将文件处理与进度报告解耦,使系统更加灵活和可维护。
系统的核心优势在于:
- 实时进度反馈:通过SSE技术向前端实时推送进度信息
- 多阶段处理:支持AI模型的多阶段调用,精确追踪每个阶段的进度
- 灵活配置:支持通过配置文件自定义阶段标识符和处理行为
- 完善的错误处理:全面的异常处理和资源清理机制
- 易于集成:前端只需少量代码即可集成该功能
通过这一设计,我们解决了长时间运行任务缺乏进度反馈的问题,提供了更好的用户体验,同时提高了系统的可靠性和可维护性。
相关文章:
AI模型多阶段调用进度追踪系统设计文档
AI模型多阶段调用进度追踪系统设计文档 一、系统概述 为解决AI模型处理大型文件时响应时间长的问题,我们设计并实现了一套异步进度追踪系统。该系统采用Server-Sent Events (SSE) 技术,建立从服务器到客户端的单向实时通信通道,使前端能够实…...
[MSPM0开发]最新版ccs20.0安装、配置及导入第一个项目
一、ccs20.0 下载与安装 Code Composer Studio™ 集成式开发环境 (IDE),适用于 TI 微控制器和处理器的集成开发环境 (IDE)。它包含一整套丰富的工具,用于构建、调试、分析和优化嵌入式应用。 ccs下载地址 链接 安装比较简单,在次略过。 二、…...
Win10怎么关闭远程控制?
对于Windows 10用户来说,Win10关闭远程桌面可以有效防止不必要的远程连接,从而保护个人数据和系统安全。那么,Win10怎么关闭远程控制功能呢?接下来,我们将详细介绍Win10关闭远程控制的具体操作步骤。 步骤1.双击桌面上…...
AI重构知识生态:大模型时代的学习、创作与决策革新
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:从知识的获取到知识的共生 过去,我们对“知识”的理解,大多依赖书籍、老师、经验和专业的培训体系。而在大语言模型(Large Language Models, LLM)崛起之后,AI成为了一种新的“知识界面”:…...
牛客 小红杀怪
通过枚举所有使用y技能的次数来枚举出所有方案,选出最合适的 #include<iostream> #include<cmath> #include<algorithm> using namespace std;int a, b, x, y; int ans500;int main() {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>&…...
Spring入门概念 以及入门案例
Spring入门案例 Springspring是什么spring的狭义与广义spring的两个核心模块IoCAOP Spring framework特点spring入门案例不用new方法,如何使用返回创建的对象 容器:IoC控制反转依赖注入 Spring spring是什么 spring是一款主流的Java EE轻量级开源框架 …...
SpringAI调用硅基流动免费模型
一、引入Spring AI 新建一个Spring Boot的工程,在工程中引入Spring AI的依赖,Spring AI支持Ollma、类OpenAI的接口,这两个引入的pom不一样,这里示例中是使用的硅基流动的模型 <!-- Spring Boot版本要 2.x 或者 3.x以上-->…...
Java 开发中主流安全框架的详细对比,涵盖 认证、授权、加密、安全策略 等核心功能,帮助开发者根据需求选择合适的方案
以下是 Java 开发中主流安全框架的详细对比,涵盖 认证、授权、加密、安全策略 等核心功能,帮助开发者根据需求选择合适的方案: 1. 主流安全框架对比表 框架名称类型核心功能适用场景优点缺点官网/文档Spring Security企业级安全框架认证、授…...
【TVM教程】在支持 CMSIS-NN 的 Arm(R) Cortex(R)-M55 CPU 和 Ethos(TM)-U55 NPU 裸机上运行 TVM
Apache TVM是一个深度的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →https://tvm.hyper.ai/ 作者:Grant Watson 本节使用示例说明如何使用 TVM 在带有 CMSIS-NN 的 Arm Cortex-M55 CPU 和 Ethos™-U55 NPU 的…...
【Ai/Agent】Windows11中安装CrewAI过程中的错误解决记录
CrewAi是什么,可以看之下之前写的 《初识CrewAI多智能代理团队协框架》 (注:这篇是基于linux系统下安装实践的) 基于以下记录解决问题后,可以再回到之前的文章继续进行CrewAI的安装 遇到问题 在windows系统中安装 CrewAi 不管是使用 pip 或者…...
洛谷 P11962:[GESP202503 六级] 树上漫步 ← dfs + 邻接表
【题目来源】 https://www.luogu.com.cn/problem/P11962 【题目描述】 小 A 有一棵 n 个结点的树,这些结点依次以 1,2,⋯,n 标号。 小 A 想在这棵树上漫步。具体来说,小 A 会从树上的某个结点出发,每⼀步可以移动到与当前结点相邻的结点&…...
Linux shell脚本编程
什么是Shell程序设计? 也就是给计算机发命令,让它帮你做事,你通过shell 的小工具,用键盘输入指令,linux就会根据这些指令去执行任务,就像你法号一个指令一样。 shell的强大之处? 文件处理&a…...
嵌入式硬件篇---Uart和Zigbee
文章目录 前言一、UART(通用异步收发传输器)1. 基本概念2. 工作原理帧结构起始位数据位校验位停止位 异步通信波特率 3. 特点优点缺点 4. 典型应用 二、ZigBee1. 基本概念2. 技术细节工作频段2.4GHz868MHz 网络拓扑星型网络网状网络簇状网络 协议栈物理层…...
Linux Makefile-概述、语句格式、编写规则、多文件编程、Makefile变量分类:自定义变量、预定义变量
目录 1.make 1.1 make 命令格式 2.Makefile 核心概念 2.1创建并运行 Makefile步骤 3. Makefile编写 3.1最基础Makefile 3.1.1使用默认make命令 3.1.2使用make -f 命令 3.1.3 gcc编译常用组合选项 3.1.4 make 和 make all区别 3.1.4.1 all 是默认目标 3.1.4.2 al…...
Kotlin日常使用函数记录
文章目录 前言字符串集合1.两个集合的差集2.集合转数组2.1.集合转基本数据类型数组2.2.集合转对象数组 Map1.合并Map1.1.使用 操作符1.2.使用 操作符1.3.使用 putAll 方法1.4.使用 merge 函数 前言 记录一些kotlin开发中,日常使用的函数和方式之类的,…...
【JavaScript】异步编程
个人主页:Guiat 归属专栏:HTML CSS JavaScript 文章目录 1. 异步编程基础1.1 同步与异步1.1.1 同步执行1.1.2 异步执行 1.2 JavaScript 事件循环 2. 回调函数2.1 基本回调模式2.2 错误处理2.3 回调地狱 3. Promise3.1 Promise 基础3.2 Promise 链式调用3…...
批量合并多张 jpg/png 图片为长图或者 PDF 文件,支持按文件夹合并图片
我们经常会碰到需要将多张图片拼成一张图片的场景,比如将多张图片拼成九宫格图片,或者将多张图片拼成一张长图。还有可能会碰到需要将多张图片合并成一个完整的 PDF 文件来方便我们进行打印或者传输等操作。那这些将图片合并成一张图片或者一个完整的文档…...
使用docker 安装向量数据库Milvus
Miluvs 官网 www.milvus.io/ https://milvus.io/docs/zh/install_standalone-docker-compose-gpu.md 一、基本概念 向量数据库:Milvus是一款云原生向量数据库,它支持多种类型的向量,如浮点向量、二进制向量等,并且可以处理大规模…...
在线PDF文件拆分工具,小白工具功能实用操作简单,无需安装的文档处理工具
小白工具中的在线 PDF 文件拆分工具是一款功能实用、操作便捷的文档处理工具,以下是其具体介绍: 操作流程 上传 PDF 文档:打开小白工具在线PDF文件拆分工具 - 快速、免费拆分PDF文档 - 小白工具的在线 PDF 文件拆分页面,通过点击 …...
Blender画图——阵列使用
如图我需要多个图示的图形,并且排成一个阵列效果。 如图依次点击效果。不要用相对偏移,要用恒定偏移。 如图设置数量。 如图完成x方向的画图后,我们需要在y方向再用一个阵列。...
VSCode 常用快捷键
Visual Studio Code (VSCode) 提供了许多快捷键,以帮助开发者提高编码效率。以下是一些常用的 VSCode 快捷键,这些快捷键适用于大多数操作系统,但在 macOS 上可能会有所不同(通常是将 Ctrl 替换为 Cmd)。 1. 文件和编…...
缓存相关问题
Redis 持久化机制 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题 热点数据和冷数据是什么 Memcache与Redis的区别都有哪些? 单线程的redis为什么这么快 redis的数据类型,以及每种数据类型的使用场景,Redis 内部结构 redis的过期策略以及内存淘汰机制 Redis 为什么…...
每日一题(小白)暴力娱乐篇22
为什么要经常学习暴力和一些娱乐呢?因为对于我们来说,暴力是最直接的方式是肯定能满足一部分答案的方法,娱乐是为了让算法变得更有趣,你愿意多去尝试多去练习,这才是最要紧的。 由题意知,就是计算两个数字…...
深入理解 Vuex:核心概念、API 详解与最佳实践
目录 Vuex 简介核心概念与工作流程核心 API 详解模块化开发 (modules)插件(Plugins)与扩展高级技巧与最佳实践 Vuex 简介 Vuex 是 Vue.js 的官方状态管理库,专为复杂应用设计,用于集中管理所有组件的共享状…...
成为一种国家战略范畴的新基建的智慧园区开源了。
智慧园区场景视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界…...
拜特科技助力科达制造,资金管理信息化迈入新阶段
近日,拜特科技成功签约科达制造股份有限公司(以下简称“科达制造”)资金管理系统升级项目。科达制造通过资金管理系统的不断迭代升级和优化,能够更加高效地管理和运用资金,提高企业的资金利用效率,满足企业…...
每日一题(小白)暴力娱乐篇20
这个题用瞪眼法解决,snakeaekns 代码如下👇 public static void main(String[] args) {Scanner scannew Scanner(System.in);System.out.println("aekns");scan.close();} 第二种方式:将snack拆解,按照大小进行排序。…...
Flutter iOS 项目中 VolumeControllerPlugin 报错解决方案
Flutter iOS 项目中 VolumeControllerPlugin 报错解决方案 在开发 Flutter 应用时,有时会遇到 iOS 项目构建失败的情况,其中一种较为常见的错误是与 VolumeControllerPlugin 相关的报错,错误信息如下: Could not build the prec…...
Java实战报错 tcp
为什么报错tcp Preview 从图片中的错误信息来看,程序遇到了 java.net.BindException,具体错误信息是 "Address already in use: bind"。这意味着你的程序试图绑定到一个已经被其他进程占用的端口(在本例中是9999端口)。…...
【补题】P10424 [蓝桥杯 2024 省 B] 好数(数位dp)
题意: 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位……)上的数字是奇数,偶数位(十位、千位、十万位……)上的数字是偶数,我们就称之为“好数”。 给定一个正整数 N…...
控制 ElementUI el-table 树形表格多选框的显示层级
1、你可以通过 selectable 属性来控制哪些行可以选择(显示多选框) <el-table:data"tableData"row-key"id"default-expand-all:tree-props"{children: children, hasChildren: hasChildren}"select"handleSelect&…...
今日行情明日机会——20250409
今日行情还需要考虑关税对抗~ 2025年4月8日涨停的主要行业方向分析 1. 军工(19家涨停) 细分领域:国防装备、航空航天、军民融合。催化因素:国家安全战略升级、国防预算增加、重大军工项目落地预期。 2. 免税(15家涨…...
基础知识补充篇:什么是DAPP前端连接中的provider
专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读352次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你想要知道区块…...
47常用控件_QWidget的toolTip属性
一个 GUI 程序,界面比较复杂, 按啥的很多~~ 当你把鼠标悬停到这个控件上的时候,就能弹出一个提示~~ setToolTip:设置提示内容 setToolTipDuring:设置提示的时间 toolTip 只是给用户看的.在代码中一般不需要获取到 toolTip. 代码示例: 设置按钮的 toolT…...
解密工业控制柜:认识关键硬件(PLC)
前言 作为一名视觉开发工程师,我们不仅要做到做好自己的工作,我们更需要在工业现场学习更多知识,最近网上流传很多,“教会徒弟,饿死师傅”;在自动化行业中,在项目下来很忙的时候,我们…...
PDF编辑,小白工具功能丰富多样,在线无需下载,操作便捷,新手小白必备
在当今数字化办公和学习的时代,PDF 文件的使用极为广泛,而小白工具的在线 PDF 浏览器以其强大且丰富的功能,成为了一款不可多得的优质 PDF 阅读工具,PDF编辑,在线无需下载,操作便捷,新手小白必备以下为您详细推荐: 功能…...
网络安全公司推荐:F5荣膺IDC全球Web应用与API防护领导者
API的广泛使用正推动安全实践的重大变革。研究表明,有41%的企业管理的API数量至少与应用数量相等,因此企业亟需实现全面的API防护。近日,IDC发布《IDC MarketScape:2024年全球Web应用和API防护企业平台供应商评估报告》࿰…...
WPF轮播图动画交互 动画缩放展示图片
WPF轮播图动画交互 动画缩放展示图片 效果如下图: XAML代码: <Window x:Class"Caroursel.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/20…...
玩转Docker | 使用Docker安装FileDrop文件共享工具
玩转Docker | 使用Docker安装FileDrop文件共享工具 前言一、FileDrop介绍FileDrop简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署FileDrop服务下载镜像创建容器检查容器状态检查服务端口安全设置四、访问FileDrop应用创建名称五、测试与使用…...
实战篇-主时钟约束
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据 总结 前言 这是对B站傅里叶的猫视频时钟约束的笔记 一、主时钟约束 report_clock_networks …...
Oracle JDBC驱动 ojdbc14:使用指南与版本说明(附资源下载)
ojdbc14 是 Oracle 公司提供的 JDBC(Java 数据库连接)驱动程序,用于连接 Java 应用程序与 Oracle 数据库。 ojdbc14.jar包已下载:https://pan.quark.cn/s/5ee7841dcd9c 关键信息 用途:使 Java 应用程序能够连接 Orac…...
spring mvc 异常处理中@RestControllerAdvice 和 @ControllerAdvice 对比详解
RestControllerAdvice 和 ControllerAdvice 对比详解 1. 基本概念 注解等效组合核心作用ControllerAdviceComponent RequestMapping(隐式)定义全局控制器增强类,处理跨控制器的异常、数据绑定或全局响应逻辑。RestControllerAdviceControll…...
单元测试原则之——不要过度模拟
什么是过度模拟? 过度模拟(over-mocking)是指在单元测试中,模拟了太多依赖项,甚至模拟了本不需要模拟的简单对象或行为。过度模拟会导致: 测试代码变得复杂,难以阅读和维护。测试逻辑偏离了实际业务逻辑,无法验证真实代码的行为。忽略了被测单元与依赖项之间的真实交互…...
云轴科技ZStackCTO王为:AI Infra平台具备解决私有化AI全栈难题能力
4月1-2日,2025中国生成式AI大会在北京举办,该会议已成为国内AI领域最具影响力的产业峰会之一。云轴科技ZStack CTO王为受邀在“大模型”峰会上发表主题为《AI 原生实践:企业实际场景的 AI 赋能与 Infra 实践探索》演讲,并参加《De…...
牛客 小苯的Z串匹配
注意数组元素是0的情况 #include<iostream> using namespace std;int t; const int N2e510;int main() {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>t;while(t){long long n;cin>>n;long long a[N];for(int i0; i<n; i) cin>>a[i];stri…...
拜特科技受邀参加跨境电商行业分会,共探资金管理数字化与AI应用新路径
3月12日,由广东省首席信息官协会主办的“跨境电商行业分会——走进绿联科技”活动在深圳绿联科技圆满举行。作为专注于金融科技与资金管理数字化解决方案的领先提供商,拜特科技受邀参加此次会议,与行业精英共同探讨跨境电商企业的资金管理数字…...
贪心算法(17)(java)可被三整除的最大整数和
给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。 示例 1: 输入:nums [3,6,5,1,8] 输出:18 解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。 …...
架构师面试(二十八):业务建模
问题 今天我们撇开纯技术,聊一下关于【业务建模】的话题。 何为业务建模?即通过易于理解的模型将业务中的关键问题准确表达出来。 业务建模是需求分析环节乃至整个软件生命周期中非常关键的一环,它几乎决定了软件的开发周期和成本。下面关…...
求教:vue中的find()函数的用法this.$set
需求:为了实现联动,当我在选择问题标题之后,后面几列的内容就会自动联动显示 方案一: 选完之后 直接 是this.questionList[index] this.selected; 这样的效果虽然改动了数组,但是页面上没有显示出来实际数组的内容 …...
常见算法模板总结
文章目录 一、二叉树1. DFS2. BFS 二、回溯模板三、记忆化搜索四、动态规划1. 01背包朴素版本滚动数组优化 2. 完全背包朴素版本滚动数组优化 3. 最长递增子序列LIS朴素版本贪心二分优化 4. 最长公共子序列5. 最长回文子串 五、滑动窗口六、二分查找七、单调栈八、单调队列九、…...