对langChain4j的补充
对于chatmemory的补充MessageWindowChatMemory是 LangChain4j 提供的「消息窗口对话记忆」组件它本身是内存级的缓存层控制最多存 20 条消息但通过绑定redisChatMemoryStoreRedis 实现的ChatMemoryStore所有对话记忆最终会持久化到 Redis而非仅存于 JVM 内存。现在配置类配置Bean public RedisChatMemoryStore redisChatMemoryStore() { RedisChatMemoryStore.Builder builder RedisChatMemoryStore.builder() .host(host) // 从配置文件读取 Redis 地址 .port(port) // 端口 .password(password) // 密码 .ttl(ttl); // 记忆的过期时间秒/毫秒由配置决定 if (StrUtil.isNotBlank(password)) { builder.user(default); } return builder.build(); }业务中这样用 这就是获取历史消息从redis中通过appid为条件MessageWindowChatMemory chatMemory MessageWindowChatMemory .builder() .id(appId) .chatMemoryStore(redisChatMemoryStore) .maxMessages(20) .build();关于历史消息怎么存的问题如下public FluxString chatToGenCode(Long appId, String message, User loginUser) { // 1. 参数校验 ThrowUtils.throwIf(appId null || appId 0, ErrorCode.PARAMS_ERROR, 应用 ID 错误); ThrowUtils.throwIf(StrUtil.isBlank(message), ErrorCode.PARAMS_ERROR, 提示词不能为空); // 2. 查询应用信息 App app this.getById(appId); ThrowUtils.throwIf(app null, ErrorCode.NOT_FOUND_ERROR, 应用不存在); // 3. 权限校验仅本人可以和自己的应用对话 if (!app.getUserId().equals(loginUser.getId())) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR, 无权限访问该应用); } // 4. 获取应用的代码生成类型 String codeGenType app.getCodeGenType(); CodeGenTypeEnum codeGenTypeEnum CodeGenTypeEnum.getEnumByValue(codeGenType); if (codeGenTypeEnum null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, 应用代码生成类型错误); } // 5. 在调用 AI 前先保存用户消息到数据库中 chatHistoryService.addChatMessage(appId, message, ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId()); // 6. 设置监控上下文用户 ID 和应用 ID MonitorContextHolder.setContext( MonitorContext.builder() .userId(loginUser.getId().toString()) .appId(appId.toString()) .build() ); // 7. 调用 AI 生成代码流式 FluxString codeStream aiCodeGeneratorFacade.generateAndSaveCodeStream(message, codeGenTypeEnum, appId); // 8. 收集 AI 响应的内容并且在完成后保存记录到对话历史 return streamHandlerExecutor.doExecute(codeStream, chatHistoryService, appId, loginUser, codeGenTypeEnum) .doFinally(signalType - { // 流结束时清理无论成功/失败/取消 MonitorContextHolder.clearContext(); });// 5. 在调用 AI 前先保存用户消息到数据库中 chatHistoryService.addChatMessage(appId, message, ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId());这里就是把消息存入数据库中。再看这里面的代码。Override public boolean addChatMessage(Long appId, String message, String messageType, Long userId) { // 基础校验 ThrowUtils.throwIf(appId null || appId 0, ErrorCode.PARAMS_ERROR, 应用ID不能为空); ThrowUtils.throwIf(StrUtil.isBlank(message), ErrorCode.PARAMS_ERROR, 消息内容不能为空); ThrowUtils.throwIf(StrUtil.isBlank(messageType), ErrorCode.PARAMS_ERROR, 消息类型不能为空); ThrowUtils.throwIf(userId null || userId 0, ErrorCode.PARAMS_ERROR, 用户ID不能为空); // 验证消息类型是否有效 ChatHistoryMessageTypeEnum messageTypeEnum ChatHistoryMessageTypeEnum.getEnumByValue(messageType); ThrowUtils.throwIf(messageTypeEnum null, ErrorCode.PARAMS_ERROR, 不支持的消息类型); // 插入数据库 ChatHistory chatHistory ChatHistory.builder() .appId(appId) .message(message) .messageType(messageType) .userId(userId) .build(); return this.save(chatHistory); }妥妥的存入数据库中根据appid存的。再看这个代码这是最后的业务代码private AiCodeGeneratorService createAiCodeGeneratorService(long appId, CodeGenTypeEnum codeGenType) { log.info(为 appId: {} 创建新的 AI 服务实例, appId); // 根据 appId 构建独立的对话记忆 MessageWindowChatMemory chatMemory MessageWindowChatMemory .builder() .id(appId) .chatMemoryStore(redisChatMemoryStore) .maxMessages(20) .build(); // 从数据库中加载对话历史到记忆中 chatHistoryService.loadChatHistoryToMemory(appId, chatMemory, 20); return switch (codeGenType) { // Vue 项目生成使用工具调用和推理模型 case VUE_PROJECT - { // 使用多例模式的 StreamingChatModel 解决并发问题 StreamingChatModel reasoningStreamingChatModel SpringContextUtil.getBean(reasoningStreamingChatModelPrototype, StreamingChatModel.class); yield AiServices.builder(AiCodeGeneratorService.class) .chatModel(chatModel) .streamingChatModel(reasoningStreamingChatModel) .chatMemoryProvider(memoryId - chatMemory) .tools(toolManager.getAllTools()) // 处理工具调用幻觉问题 .hallucinatedToolNameStrategy(toolExecutionRequest - ToolExecutionResultMessage.from(toolExecutionRequest, Error: there is no tool called toolExecutionRequest.name()) ) .maxSequentialToolsInvocations(20) // 最多连续调用 20 次工具 .inputGuardrails(new PromptSafetyInputGuardrail()) // 添加输入护轨 // .outputGuardrails(new RetryOutputGuardrail()) // 添加输出护轨为了流式输出这里不使用 .build(); } // HTML 和 多文件生成使用流式对话模型 case HTML, MULTI_FILE - { // 使用多例模式的 StreamingChatModel 解决并发问题 StreamingChatModel openAiStreamingChatModel SpringContextUtil.getBean(streamingChatModelPrototype, StreamingChatModel.class); yield AiServices.builder(AiCodeGeneratorService.class) .chatModel(chatModel) .streamingChatModel(openAiStreamingChatModel) .chatMemory(chatMemory) .inputGuardrails(new PromptSafetyInputGuardrail()) // 添加输入护轨 // .outputGuardrails(new RetryOutputGuardrail()) // 添加输出护轨为了流式输出这里不使用 .build(); } default - throw new BusinessException(ErrorCode.SYSTEM_ERROR, 不支持的代码生成类型: codeGenType.getValue()); }; }MessageWindowChatMemory chatMemory MessageWindowChatMemory .builder() .id(appId) .chatMemoryStore(redisChatMemoryStore) .maxMessages(20) .build();先与redis进行绑定再看最关键的代码// 从数据库中加载对话历史到记忆中 chatHistoryService.loadChatHistoryToMemory(appId, chatMemory, 20);代码如下Override public int loadChatHistoryToMemory(Long appId, MessageWindowChatMemory chatMemory, int maxCount) { try { QueryWrapper queryWrapper QueryWrapper.create() .eq(ChatHistory::getAppId, appId) .orderBy(ChatHistory::getCreateTime, false) .limit(1, maxCount); ListChatHistory historyList this.list(queryWrapper); if (CollUtil.isEmpty(historyList)) { return 0; } // 反转列表确保按照时间正序老的在前新的在后 historyList historyList.reversed(); // 按照时间顺序将消息添加到记忆中 int loadedCount 0; // 先清理历史缓存防止重复加载 chatMemory.clear(); for (ChatHistory history : historyList) { if (ChatHistoryMessageTypeEnum.USER.getValue().equals(history.getMessageType())) { chatMemory.add(UserMessage.from(history.getMessage())); } else if (ChatHistoryMessageTypeEnum.AI.getValue().equals(history.getMessageType())) { chatMemory.add(AiMessage.from(history.getMessage())); } loadedCount; } log.info(成功为 appId: {} 加载 {} 条历史消息, appId, loadedCount); return loadedCount; } catch (Exception e) { log.error(加载历史对话失败appId: {}, error: {}, appId, e.getMessage(), e); // 加载失败不影响系统运行只是没有历史上下文 return 0; } }QueryWrapper queryWrapper QueryWrapper.create() .eq(ChatHistory::getAppId, appId) .orderBy(ChatHistory::getCreateTime, false) .limit(1, maxCount); ListChatHistory historyList this.list(queryWrapper); if (CollUtil.isEmpty(historyList)) { return 0; } // 反转列表确保按照时间正序老的在前新的在后 historyList historyList.reversed();就是从数据库查maxcount条数据。// 先清理历史缓存防止重复加载 chatMemory.clear();就是先清理历史会话消息因为我们用的是redis存的所以也相当于删除redis里面的数据。看这个clear的源码。这里其实可以把chatmemory当成redis。for (ChatHistory history : historyList) { if (ChatHistoryMessageTypeEnum.USER.getValue().equals(history.getMessageType())) { chatMemory.add(UserMessage.from(history.getMessage())); } else if (ChatHistoryMessageTypeEnum.AI.getValue().equals(history.getMessageType())) { chatMemory.add(AiMessage.from(history.getMessage())); } loadedCount; }这就是存储数据到chatmemory中下面直接看clear和add的源码先看clear的} public void clear() { this.store.deleteMessages(this.id); }选redis的。public void deleteMessages(Object memoryId) { this.client.del(this.toRedisKey(memoryId)); }public long del(String key) { return (Long)this.executeCommand(this.commandObjects.del(key)); }这就是删除数据会不会有人问id也没有传啊其实上面MessageWindowChatMemory chatMemory MessageWindowChatMemory.builder().id(appId).chatMemoryStore(redisChatMemoryStore).maxMessages(20).build();这里已经把redis的id给你绑定好了不管删除还是add都是这个id。再看add的源码public void add(ChatMessage message) { ListChatMessage messages this.messages(); if (message instanceof SystemMessage) { OptionalSystemMessage systemMessage findSystemMessage(messages); if (systemMessage.isPresent()) { if (((SystemMessage)systemMessage.get()).equals(message)) { return; } messages.remove(systemMessage.get()); } } messages.add(message); ensureCapacity(messages, this.maxMessages); this.store.updateMessages(this.id, messages); }如果添加的是UserMessage用户消息或AiMessageAI 消息跳过 SystemMessage 的判断逻辑直接追加到消息列表触发容量控制超出 max 则删旧同步到存储。依旧选择redis的。public void updateMessages(Object memoryId, ListChatMessage messages) { String json ChatMessageSerializer.messagesToJson((List)ValidationUtils.ensureNotEmpty(messages, messages)); String key this.toRedisKey(memoryId); String res; if (this.ttl 0L) { res this.client.setex(key, this.ttl, json); } else { res this.client.set(key, json); } if (!OK.equals(res)) { throw new RedisChatMemoryStoreException(Set memory error, msg res); } }即存入redis中。所以流程就是先存数据库再从数据库中查然后存入chatmemoryrediskey为appid中此前还有一步是clear先删除原来的chatmemory以便填入最新的20条。完整对象的详解在createAiCodeGeneratorService 里面// Vue 项目生成使用工具调用和推理模型 case VUE_PROJECT - { // 使用多例模式的 StreamingChatModel 解决并发问题 StreamingChatModel reasoningStreamingChatModel SpringContextUtil.getBean(reasoningStreamingChatModelPrototype, StreamingChatModel.class); yield AiServices.builder(AiCodeGeneratorService.class) .chatModel(chatModel) .streamingChatModel(reasoningStreamingChatModel) .chatMemoryProvider(memoryId - chatMemory) .tools(toolManager.getAllTools()) // 处理工具调用幻觉问题 .hallucinatedToolNameStrategy(toolExecutionRequest - ToolExecutionResultMessage.from(toolExecutionRequest, Error: there is no tool called toolExecutionRequest.name()) ) .maxSequentialToolsInvocations(20) // 最多连续调用 20 次工具 .inputGuardrails(new PromptSafetyInputGuardrail()) // 添加输入护轨 // .outputGuardrails(new RetryOutputGuardrail()) // 添加输出护轨为了流式输出这里不使用 .build(); }StreamingChatModel reasoningStreamingChatModel SpringContextUtil.getBean(reasoningStreamingChatModelPrototype, StreamingChatModel.class);此为获取相应的bean下面看这个bean的定义import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * AI大模型配置类创建StreamingChatModel相关Bean */ Configuration // 标记为配置类Spring会扫描并创建其中的Bean public class ChatModelConfig { /** * 定义名称为 reasoningStreamingChatModelPrototype 的原型Bean * Scope(prototype)原型作用域每次getBean都返回新实例适配不同请求的个性化配置 */ Bean(name reasoningStreamingChatModelPrototype) // 指定Bean名称 Scope(prototype) // 原型作用域关键和单例singleton区分 public StreamingChatModel reasoningStreamingChatModelPrototype() { // 构建StreamingChatModel实例以OpenAI为例也可能是讯飞/百度等国产模型 return OpenAiStreamingChatModel.builder() .apiKey(你的大模型API密钥) .modelName(gpt-3.5-turbo) .temperature(0.7) .build(); } // 其他Bean定义... }就是根据这个reasoningStreamingChatModelPrototype, StreamingChatModel.class去找相应的bean吗为什么要把bean区分呢是因为一些问题的难度不同可能你要提升模型就得选模型高的那个bean。接着往下yield AiServices.builder(AiCodeGeneratorService.class)核心作用启动AiCodeGeneratorService接口的代理实例构建流程。底层逻辑AiServices是 LangChain4j 的核心工具能基于「业务接口AiCodeGeneratorService」动态生成代理类无需你手动写接口实现yieldJava 生成器语法通常用在Iterable/Stream中表示 “产出” 这个构建好的 AI 服务实例供后续调用AiCodeGeneratorService.class指定要构建的 AI 服务接口接口中定义了generateHtmlCodeStream、generateVueCode等代码生成方法。.chatModel(chatModel)作用配置「非流式聊天模型」比如ChatModel接口的实现。业务价值流式模型streamingChatModel用于长文本 / 代码的流式输出非流式模型用于短文本交互比如工具调用前的简短推理、参数校验两者互补提升 AI 交互的效率短文本用非流式更快长文本用流式更友好。.streamingChatModel(reasoningStreamingChatModel)作用配置「流式聊天模型」就是第一步获取的reasoningStreamingChatModel。业务价值代码生成是典型的 “长文本输出场景”流式模型能「逐行返回代码」比如先返回template再返回script前端可以实时渲染避免用户等待整个代码生成完成这是代码生成平台 “流式输出” 功能的核心配置。.chatMemoryProvider(memoryId - chatMemory)作用配置「对话记忆提供者」指定 “根据记忆 IDmemoryId获取对应的对话记忆chatMemory”。底层逻辑memoryId对应你的appId每个应用的唯一标识Lambda 表达式memoryId - chatMemory表示 “无论传入哪个 memoryId都返回当前绑定的 chatMemory 实例”保证每个应用的对话记忆隔离核心价值让 AI 能读取当前应用的历史对话比如用户之前要求 “生成登录页”后续要求 “加个验证码”AI 能基于记忆理解上下文。.tools(toolManager.getAllTools())作用注册 AI 可调用的所有工具Tool。业务场景toolManager.getAllTools()获取工具管理器中注册的所有工具比如代码语法校验工具、文件保存工具、Vue 工程初始化工具、HTML 模板生成工具等AI 生成代码时会根据需求自动调用这些工具比如生成完代码后自动调用 “语法校验工具” 检查是否有语法错误这是 “AI 智能体” 的核心能力不仅能生成文本还能调用工具完成实际操作。(5).hallucinatedToolNameStrategy(...)作用处理「工具调用幻觉」AI 虚构不存在的工具名称。问题背景AI 可能因为模型偏差调用一个你没注册的工具比如你只有checkCodeSyntax工具AI 却调用checkCodeError导致工具调用失败解决方案当 AI 调用不存在的工具时执行 lambda 表达式中的逻辑返回一个错误提示Error: there is no tool called xxx并封装成ToolExecutionResultMessage让 AI 感知到 “调用失败”避免流程中断。(6).maxSequentialToolsInvocations(20)作用限制「最大连续工具调用次数」。业务价值防止 AI 无限循环调用工具比如代码校验失败→重新生成→再校验→再失败无限循环限制为 20 次后超过次数会停止调用保护系统资源避免 CPU / 内存耗尽。(7).inputGuardrails(new PromptSafetyInputGuardrail())作用添加「输入防护栏」过滤有害 / 违规的用户输入。业务价值PromptSafetyInputGuardrail是自定义的输入防护类比如包含敏感词过滤、恶意 prompt 检测比如用户输入 “生成钓鱼网站代码”防护栏会拦截该请求避免 AI 生成违规内容这是平台合规性的核心配置防止滥用 AI。(8) 注释掉的.outputGuardrails(...)作用原本用于添加「输出防护栏」校验 AI 输出的内容是否合规。为什么注释输出防护栏需要「完整获取 AI 输出后再校验」但代码生成是「流式输出」逐段返回无法提前获取完整输出如果启用会阻断流式返回必须等所有内容生成完才能校验影响用户体验所以注释掉。(9).build()作用完成所有配置构建最终的AiCodeGeneratorService代理实例。底层逻辑LangChain4j 会根据上述配置动态生成AiCodeGeneratorService接口的实现类封装「模型调用、记忆读取、工具调用、安全防护」等所有逻辑后续调用aiCodeGeneratorService.generateHtmlCodeStream()时实际执行的是这个动态生成的代理方法。用户发起 “生成 HTML 登录页” 请求 → 代码构建 AI 服务实例获取独立的流式模型实例避免多请求串配置配置流式模型保证代码流式返回 对话记忆读取历史需求注册代码生成相关工具支持语法校验、文件保存等配置工具调用防护处理幻觉、限制调用次数配置输入防护过滤违规请求构建 AI 服务实例处理用户请求并流式返回代码chatToGenCode中最后的响应部分return streamHandlerExecutor.doExecute(codeStream, chatHistoryService, appId, loginUser, codeGenTypeEnum) .doFinally(signalType - { // 流结束时清理无论成功/失败/取消 MonitorContextHolder.clearContext(); });下面直接来看doExecute方法public FluxString doExecute(FluxString originFlux, ChatHistoryService chatHistoryService, long appId, User loginUser, CodeGenTypeEnum codeGenType) { return switch (codeGenType) { case VUE_PROJECT - // 使用注入的组件实例 jsonMessageStreamHandler.handle(originFlux, chatHistoryService, appId, loginUser); case HTML, MULTI_FILE - // 简单文本处理器不需要依赖注入 new SimpleTextStreamHandler().handle(originFlux, chatHistoryService, appId, loginUser); }; }case VUE_PROJECT - // 使用注入的组件实例 jsonMessageStreamHandler.handle(originFlux, chatHistoryService, appId, loginUser);先看这一行。看handle方法public FluxString handle(FluxString originFlux, ChatHistoryService chatHistoryService, long appId, User loginUser) { StringBuilder aiResponseBuilder new StringBuilder(); return originFlux .map(chunk - { // 收集AI响应内容 aiResponseBuilder.append(chunk); return chunk; }) .doOnComplete(() - { // 流式响应完成后添加AI消息到对话历史 String aiResponse aiResponseBuilder.toString(); chatHistoryService.addChatMessage(appId, aiResponse, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId()); }) .doOnError(error - { // 如果AI回复失败也要记录错误消息 String errorMessage AI回复失败: error.getMessage(); chatHistoryService.addChatMessage(appId, errorMessage, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId()); }); } }就是会对ai输出的话进行一个收集aiResponseBuilder.append(chunk);然后转成String类型的存储到chatmemory中String aiResponse aiResponseBuilder.toString(); chatHistoryService.addChatMessage(appId, aiResponse, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId())。然后// 如果AI回复失败也要记录错误消息 String errorMessage AI回复失败: error.getMessage(); chatHistoryService.addChatMessage(appId, errorMessage, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId());即使失败也存入到chatmemory中

相关新闻

用Anki高效啃透华工通信原理:自制考点卡片模板分享(含HDB3编码/匹配滤波器真题)

用Anki高效啃透华工通信原理:自制考点卡片模板分享(含HDB3编码/匹配滤波器真题)

用Anki重塑通信原理学习:从被动记忆到主动构建知识网络 又到了期末复习季,看着那本厚厚的《通信原理》,是不是感觉知识点像散落的珍珠,捡起这颗,又丢了那颗?传统的划重点、背公式,往往陷入“一看…

2026/5/17 11:26:31 阅读更多 →
Qwen3智能字幕对齐系统技能智能体开发

Qwen3智能字幕对齐系统技能智能体开发

Qwen3智能字幕对齐系统技能智能体开发 1. 项目背景与价值 视频内容创作正在经历爆发式增长,从短视频平台到在线教育,从企业培训到自媒体内容,视频已经成为信息传递的主流形式。但在视频制作过程中,字幕处理往往是一个容易被忽视…

2026/5/17 0:54:48 阅读更多 →
CodeLlama推理加速实战:用投机解码+分页KV-Cache让生成速度提升4倍

CodeLlama推理加速实战:用投机解码+分页KV-Cache让生成速度提升4倍

CodeLlama推理加速实战:用投机解码与分页KV-Cache实现4倍性能飞跃 在构建本地AI编程助手或需要低延迟响应的AI应用时,我们常常面临一个核心矛盾:模型能力与推理速度之间的权衡。尤其是当硬件预算有限,仅有一张消费级显卡&#xff…

2026/7/6 15:02:04 阅读更多 →

最新新闻

微尺度运动图谱:非牛顿流体中突破扇贝定理的工程实践

微尺度运动图谱:非牛顿流体中突破扇贝定理的工程实践

1. 这不是教科书里的流体力学,而是微尺度世界的“游泳法则”实战笔记你有没有想过,一个比头发丝还细十倍的微型机器人,怎么在血液、黏液或者工业凝胶里游动?它没法像鱼那样甩尾巴,也没法像螺旋藻那样打转——在那种尺度…

2026/7/8 4:28:24 阅读更多 →
卡特加特AI营销超算一体机的应用场景?

卡特加特AI营销超算一体机的应用场景?

在全面了解了卡特加特AI营销超算一体机在各行业的具体应用之后,我们有必要回到一个更根本的问题:为什么这些看似各不相同行业的企业,最终都选择了同一套产品体系?答案在于,卡特加特所提供的不仅是一个解决某个具体营销…

2026/7/8 4:22:23 阅读更多 →
Ubuntu 22.04 安装超好用中文输入法rime-ice(雾凇拼音)过程记录

Ubuntu 22.04 安装超好用中文输入法rime-ice(雾凇拼音)过程记录

Ubuntu 22.04 安装超好用中文输入法rime-ice(雾凇拼音)过程记录 这篇记录一下我在 Ubuntu 22.04 上安装并配置 Fcitx5 Rime 雾凇拼音 的过程。中间踩了一些坑,尤其是 Ubuntu 22.04 的 librime 版本较旧,不能直接使用最新版雾凇拼音,所以这里…

2026/7/8 4:20:22 阅读更多 →
孤能子视角:蚂蚁灵波的“眼睛”

孤能子视角:蚂蚁灵波的“眼睛”

(在以下的与AI互动中,在EIS理论约束下,DeepSeek叫信兄,Kimi叫酷兄,我呢叫水兄。姑且当科幻小说看) 讨论源于新智元文章:12项世界第一!国产基模一战封神,11亿参数逆袭70亿大魔王 https://mp.weixin.qq.com/s…

2026/7/8 4:20:22 阅读更多 →
几百平小仓库,有必要上专业WMS系统吗?

几百平小仓库,有必要上专业WMS系统吗?

几百平小仓库,有必要上专业WMS系统吗?很多中小仓库老板都有同一个疑问:我仓库只有几百平、员工几个人、SKU不多,要不要花钱上WMS?用Excel、手工记账不行吗?实话实说:现在的中小仓库,…

2026/7/8 4:20:22 阅读更多 →
十五五新能源,不拼成本拼什么

十五五新能源,不拼成本拼什么

2026年入夏,电力行业出了两个信号。 第一个:全国21个省份实现了新能源"报量报价"参与电力现货。光伏和风电不再是"发多少电网收多少",跟火电一样,自己报量、自己报价、自己承担偏差考核。 第二个:…

2026/7/8 4:16:22 阅读更多 →

日新闻

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: …

2026/7/8 0:00:48 阅读更多 →
MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N1 到批处理的全路径 一、从"功能正确"到"性能可接受"——MyBatis 批量操作的三段式进化 MyBatis 在日常增删改查场景中几乎是无感的——实体映射直观、SQL 控制灵活。但当数据量从千级上升到十万级、百万级,许…

2026/7/8 0:00:48 阅读更多 →
工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

1. 工业负载控制方案概述在工业自动化、电机驱动和照明控制等高需求场景中,可靠地控制电感和电阻负载是核心挑战之一。TPD2015FN作为东芝的8通道高端智能功率开关IC,配合PIC18F45K22微控制器,能够构建一套稳定、高效的负载控制系统。这套组合…

2026/7/8 0:02:48 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/7 14:24:45 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/7 12:34:47 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/7 15:59:06 阅读更多 →

月新闻