目录

前言

解决思路

需求理解

MCP Server

LangGraph

本教程目标

技术栈

第一部分:构建 MCP Server - 工具服务化的基础架构

第二部分:Tools 实现

第三部分:基于 LangGraph 构建智能 Agent

第四部分:服务器和前端搭建


前言

差不多今年,"MCP""Agent" 一直都是 AI 领域的热点,尤其是 manus 的出现,显得 Agent 好像无所不能,极大的展现了 AI 的思考和执行决策的能力。

AI 不再只是单纯地回答问题,而是能够主动理解任务、规划步骤、调用工具,并最终完成目标。

但是在控件领域,控件产品基本都有很多 API, 有时候哪怕最熟练的开发者也很难清楚每个 API 的定义.

比如 SpreadJS :它提供了超过 2000 个 API,功能非常灵活,能够覆盖各种复杂场景。但对于开发者来说,光靠人工翻阅文档或记忆这些 API 显然效率不高。

如果用 LLM 帮助生成 SpreadJS API 代码,开发效率肯定会提高,不过,目前市面上常见的 LLM(ChatGPT、DeepSeek、Grok、Gemini ),在 SpreadJS 这种细分领域上能做的事情有限,简单 API 调用没什么问题,比如最基础的 setValue、getValue,但难度一上来它们很容易出现幻觉,生成一些看起来正确实则跑不了的代码。

比如从数据库取数据并构建一个仪表盘这种要调用多个 plugin 而且要进行深度使用,考虑 API 联合工作的结果的复杂需求

如果能通过一些人工投喂的知识,了解 SpreadJS 的一些冷门 API 和准确的使用方式,生成准确的代码,就可以稳定地提高开发的效率了。

那如何做呢?

解决思路

做类似 "知识库" 的功能,当 AI 想要知道什么它不懂的东西的时候,就去调用 "知识库" 来获取准确的信息,从而降低幻觉,提升代码准确度.

可以搭建一个 MCP Server 来暴露 SpreadJS 文档查询工具,再基于 LangGraph 搭建一个 Agent。该 Agent 能够自主决定是否查询文档,并在生成代码,执行代码之后进行结果校验,确保结果真正满足用户需求之后再结束逻辑。

需求理解

我们把 Agent 定义为:

能够自主理解任务、规划步骤、使用工具完成目标,并具备多轮对话和记忆能力的 AI 应用

换句话说,如果一个 AI 能听懂需求,自行分析步骤,调用工具,并完成多轮消息处理直到完成任务,就可以算作一个 Agent。

在实现上,其实并不复杂,只需要两条:

  • 支持多轮对话的 AI 对话框
  • tool call (function call)

就能拼出一个基础的 Agent 原型。

那么,为什么要额外引入 MCP Server 和 LangGraph 呢?

MCP Server

Model Context Protocol(MCP)是一个 AI 工具调用的标准化协议,最核心的优势就是打通了不同 tool call 之间的差异,用标准化的协议完成通信,而且跟 Agent 解耦,Agent 只需要通过 getTools 这种接口获取可用工具列表即可.

因此,我们会把 SpreadJS 文档查询封装成一个独立的 MCP Server,不与本项目的其他部分耦合。未来即便是别的 AI 项目,也能直接使用这套服务。

当然,MCP 也有其劣势,例如,提供工具数量太多,AI 会陷入混乱,命名不标准,导致 AI 无法理解等,我们账号下有文章分析过其中的劣势,可以自行查找阅读。整体上来说,MCP 还是一个利大于弊的技术,所以才能被大家广泛接受。

LangGraph

选择 LangGraph 的原因在于它是一个 "搭建 Agent 的脚手架",就像我们在前端不会一直用 "原生 JS+HTML+CSS" 写所有项目,而会选择 React/Vue 这样的框架来提升效率一样,LangGraph 是一个构建 Agent 的框架,可以让我们用更简单的方式构建 Agent。

在我们这篇教程中,我们使用的是 Langgraph 的 TypeScript 版本,主要原因是 SpreadJS 主要面向的是前端开发者,对 Typescript 代码更熟悉,且可以直接在前端运行,如果你对 python 更熟悉,可以用 Langgraph 的 python 包,支持的更好更全面.

本教程目标

构建一个可以理解自然语言并操作 SpreadJS 表格的智能助手,用户只需输入类似添加一个表格,范围为 A1:C4 的需求,AI 即可自主判断是否需要文档帮助,是否可以生成代码,是否已经完成目标,实现用户的任务。

技术栈

  • 后端: TypeScript + LangGraph + MCP Server
  • 前端: 原生 HTML/CSS/JavaScript(前端代码不是我们这次教程的重点,能跑就行)
  • 通信: WebSocket(实现实时双向通信)
  • AI: Langchain ChatOpenAI SDK

我们项目设置三个端口:

  • 3000 端口:前端服务,网页
  • 3001 端口: Agent 服务,AI
  • 3002 端口: MCP Server 服务

第一部分:构建 MCP Server - 工具服务化的基础架构

MCP Server 实现

首先我们来实现 MCP Server

看看核心代码:

export class MCPServer {private server: http.Server;constructor() {this.server = this.createHTTPServer();}private createHTTPServer(): http.Server {return http.createServer(async (req, res) => {const parsedUrl = url.parse(req.url!, true);if (req.method === 'GET' && parsedUrl.pathname === '/tools') {res.end(JSON.stringify({success: true,tools: this.getTools()}));} else if (req.method === 'POST' && parsedUrl.pathname === '/execute') {const { toolName, args } = await this.parseBody(req);const result = await this.executeTool(toolName, args);res.end(JSON.stringify({ success: true, result }));}});}
}

这里的设计思路很简单:getTools 拿工具列表,execute 执行工具,实现了一个 MCP Server 最基础的功能

核心 API 设计

// 获取可用工具列表
public getTools() {return Object.entries(toolDefinitions).map(([name, definition]) => ({name,description: definition.description,schemaDescription: definition.schema}));
}// 执行指定的工具
public async executeTool(toolName: string, args: any): Promise<any> {const toolImpl = toolImplementations[toolName];if (!toolImpl) {throw new Error(`Tool ${toolName} not found`);}console.log(`[MCP Server] Executing tool: ${toolName}`);const result = await toolImpl(args);return result;
}

第二部分:Tools 实现

定义与实现分离

我可以在 Agent 里写工具逻辑,但这样做有几个问题,但是我们使用的 MCP 服务不总是自己写的,所以在教程里,就养成用网络请求调用 MCP Server 是个好习惯。而且解耦之后,重构成本也更小.

包括工具的定义和实现,也考虑分开,定义用 JSON Schema 就可以了,不需要沾逻辑,在实现上再写逻辑,这样不变的定义永远不用改,逻辑上有 BUG 去改逻辑就行了。

// 工具定义层
export const toolDefinitions: Record<string, ToolDefinition> = {"api-doc-search": {description: "搜索SpreadJS相关的技术文档和API信息",schema: {type: "object",properties: {keyword: {type: "string",description: "需要搜索SpreadJS如何使用的关键词"}},required: ["keyword"]}}
};// 工具实现层
export const toolImplementations: Record<string, ToolFunction> = {"api-doc-search": async ({ keyword }: { keyword: string }) => {const encodedTopic = encodeURIComponent(keyword);const url = `https://context7.com/api/v1/llmstxt/...`;const res = await fetch(url, { /* 请求配置 */ });const data = await res.json();return processedResults;}
};

我们实现了两个核心工具:

api-doc-search:基于 Context7 的文档检索

"api-doc-search": async ({ keyword }: { keyword: string }) => {const encodedTopic = encodeURIComponent(keyword);const url = `https://context7.com/api/v1/llmstxt/developer_mescius_com-spreadjs-docs-llms.txt?type=json&tokens=100000&topic=${encodedTopic}`;const res = await fetch(url, {method: "GET",headers: {"accept": "*/*","content-type": "application/json",}});const data = await res.json();if (data && data.snippets && Array.isArray(data.snippets)) {return data.snippets.slice(0, 8).map((snippet: any, index: number) => {let content = `**${snippet.codeTitle}**\n\n`;if (snippet.codeDescription) {content += `描述: ${snippet.codeDescription}\n\n`;}if (snippet.codeList && snippet.codeList.length > 0) {content += `代码示例:\n\`\`\`javascript\n`;content += snippet.codeList[0].code;content += '\n```';}return {id: index + 1,title: snippet.codeTitle,content: content,code: snippet.codeList?.[0]?.code || '',source: snippet.pageTitle};});}
}

这个工具通过 Context7 API 搜索 SpreadJS 文档,返回结构化的搜索结果。每个结果包含标题、描述、代码示例等信息,AI 可以基于这些信息生成更准确的代码。

context7 是一个工具网站,可以通过 llms.txt 构建 RAG 索引,SpreadJS 的 docs 站点提供了 llms.txt, context7 就自动根据 llms.txt 爬取了 SpreadJS 的文档库,构建了 RAG 索引,通过 contetx7 搜索就可以拿到文档中的信息. context7 也提供了一个独立的 MCP Server, 有兴趣可以把它装到自己的 IDE 里试试.

execute-spreadjs:生成 SpreadJS API 代码

"execute-spreadjs": async ({ execute_logic, query_logic }: {execute_logic: string;query_logic: string
}) => {console.log(`[Tool Call: execute-SpreadJS] Generating code...`);const responseToFrontend = JSON.stringify({execute: execute_logic,query: query_logic,});return responseToFrontend;
}

这个工具负责生成 SpreadJS 代码。它接收两个参数:

  • execute_logic:要执行的操作代码
  • query_logic:用于验证结果的查询代码

这个思想其实是很好的,不局限于这个工具,我们写这种执行逻辑的时候,都可以想到,这种网络请求式的操作,都可以强制要求返回一个确认操作,即一个 write 操作,一个 read 操作,进行结果校验,这比单独写个 query_spreadjs 的工具再去校验一次,不就省了很多 token, 请求的时间也省下了吗.

另外,在 initSpreadJS 的时候,我们把一些变量暴露在了 window 下,这样 AI 生成的代码就可以直接 new Function 执行了,但是实际 production 环境,还是要用隔离沙箱去做,不要这样 hackcode.

工具注册与对接

在 MCP Server 中,工具的注册过程很简单:

public getTools() {return Object.entries(toolDefinitions).map(([name, definition]) => ({name,description: definition.description,schemaDescription: definition.schema}));
}

当 Agent 需要执行工具时:

public async executeTool(toolName: string, args: any): Promise<any> {const toolImpl = toolImplementations[toolName];const result = await toolImpl(args);return result;
}

更好的搜索

如果需要更强的 RAG 检索效果,可以引入 GC QA RAG, 利用这个 RAG 项目,可以生成 QA 对检测,可以自己部署,效果很好,可以自己部署一下试试.

只需要替换 api-doc-search 的实现,接口保持不变,Agent 端无需任何修改。

context7 使用的 RAG 技术我不太清楚,但是肯定用的不是 QA 对方案,其实对于查询来说,用 QA 对 query string 进行向量匹配,效果要比用原文档对 query string 进行向量匹配肯定要好的多,甚至不需要 reranking 这些步骤.

这些概念对于不了解 RAG 的人可能会稍显有些费解,如果有兴趣可以阅读一下我们账号发布的关于 RAG 的一些文章了解.

第三部分:基于 LangGraph 构建智能 Agent

LangGraph 介绍

LangGraph 的核心概念主要有几个:node、edge 和 state。

可以把它想象成一个状态机,每个 node 是一个步骤,edge 决定步骤之间怎么流转,而 state 记录上下文。这样一来,就能自然地实现多轮对话和工具调用。 

Node、Edge 与 ConditionalEdge

  • Node:工作流中的一个步骤,可以是 LLM 调用、工具执行,或者任何异步操作
  • Edge:边 - 固定的流转路径,A 节点执行完总是去 B 节点
  • ConditionalEdge:条件边 - 带条件的流转路径,根据运行时状态决定去哪个节点

Agent 状态管理

状态是什么

在 LangGraph 中,state 保存着对话历史与工具调用结果。对我们来说,messages 是最核心的上下文数据:

type AgentState = {messages: BaseMessage[];
};const workflow = new StateGraph<AgentState>({channels: {messages: {value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y),default: () => [],}},
});

这里的设计很巧妙:新的 messages 会自动追加到现有 messages 后面,形成完整的对话历史。这样 AI 就能记住之前的交互内容。

streamEvents API

streamEvents 是 LangGraph 提供的一个强大功能,让我们能够实时监控 Agent 的执行过程。相比只看最终结果,实时过程对用户体验来说更重要:

const eventStream = compiledGraph.streamEvents(initialState, {version: "v2" as const,recursionLimit: 10
});for await (const event of eventStream) {switch (event.event) {case 'on_chat_model_stream':// AI 思考的流式输出if (event.data.chunk?.content) {yield {type: 'agent_message',data: { content: event.data.chunk.content }};}break;case 'on_tool_start':// 工具开始执行yield {type: 'agent_step',data: {step: 'tools',output: { lastToolCall: { name: event.name, args: event.data.input } }}};break;case 'on_tool_end':// 工具执行完成yield {type: 'agent_step',data: {step: 'tools',output: { toolResult: event.data.output }}};break;}
}

LangChain 还推出了一个名为 LangSmith 的工具,它专门用于监控和分析 Agent 的状态以及工作流执行情况。 虽然我在本教程中没有使用它,但如果你希望更直观地观察 state 变化、事件流和决策路径,可以把 langsmith 接入项目试试。

Langgraph 工作流设计

我们采用了一个经典的 action-check 模式:

// 节点 1: action - 执行工具调用和LLM推理
workflow.addNode("action", this.actionNode.bind(this));
// 节点 2: check - AI自主决策下一步行动
workflow.addNode("check", this.checkNode.bind(this));// 设置工作流的入口node
workflow.setEntryPoint("action");// action 执行后总是去 check 进行决策
workflow.addEdge("action", "check");// check 节点执行后进行AI自主决策是执行action, 还是结束
workflow.addConditionalEdges("check", async (state: AgentState) => {const decision = await this.aiDecideNextAction(state);if (decision === "__end__") {return "__end__";} else {return "action"; // 继续执行}
});

在这个 workflow 中,我们设计了 2 个 Node, 2 个 Edge, 并将入口设置为了 action 节点.

我们用一张图来理解这个 workflow 

Node 的搭建

action node:这是主要的执行节点,负责 AI 推理和工具调用

private async actionNode(state: AgentState): Promise<{ messages: BaseMessage[] }> {const response = await this.model.invoke(state.messages);// 如果模型决定调用工具if (response.tool_calls && response.tool_calls.length > 0) {const toolMessages: ToolMessage[] = await Promise.all(response.tool_calls.map(async (toolCall) => {const tool = this.tools.find((t) => t.name === toolCall.name);const output = await tool.invoke(toolCall.args);return new ToolMessage({tool_call_id: toolCall.id!,content: typeof output === 'string' ? output : JSON.stringify(output),name: toolCall.name});}));return { messages: [response, ...toolMessages] };}return { messages: [response] };
}

check node:这个节点很简单,主要作用是触发 AI 决策,也就是把逻辑放在了条件边中,可以在这里记一些 log 之类的。

private checkNode(state: AgentState): { messages: [] } {return { messages: [] };
}

为什么使用 action-check 模式

action-check 模式的核心思想是执行与决策解耦

  1. action 节点:专注于执行任务,包括调用 AI、执行工具、处理结果等操作。
  2. check 节点:专注于决策,根据当前状态分析并决定下一步的行动方向。

逻辑与可维护性优势

  • 逻辑清晰:执行和决策分开,代码结构直观,易于维护。
  • 可控性高:可以在 check 节点加入复杂决策逻辑,而不干扰执行逻辑。
  • 便于调试:每个决策点都有明确日志和状态记录,方便排查问题。

发挥 Agent 自主性

这种模式能更好的发挥 AI 自主决策的能力,我们下面讨论。

AI 自主决策机制

这是整个系统的核心部分。我们让 AI 基于完整的对话历史自主决策下一步行动:

private async aiDecideNextAction(state: AgentState): Promise<string> {const decisionPrompt = `
基于以上完整的对话历史,决定下一步最合适的行动:1. CONTINUE_SEARCH - 如果需要更多SpreadJS技术信息
2. EXECUTE_CODE - 如果有足够信息可以生成SpreadJS代码
3. TASK_COMPLETE - 如果用户请求已完全满足请只回复上述选项之一,不需要其他解释。
`;const decisionModel = new ChatOpenAI({ /* 配置 */ });// 将决策提示添加到现有对话历史中const decisionMessages = [...state.messages, new HumanMessage(decisionPrompt)];const response = await decisionModel.invoke(decisionMessages);const decision = response.content.trim();// 根据AI决策返回路由switch (decision) {case 'CONTINUE_SEARCH':case 'EXECUTE_CODE':return "action";case 'TASK_COMPLETE':return "__end__";default:return "__end__"; // 安全默认:避免无限循环}
}

这里的关键是将决策判断交给 AI,而不是用硬编码的规则。AI 能够理解上下文,做出更智能的判断。注意要 default 直接结束避免循环.

工具集成

Agent 启动时会从 MCP Server 获取工具列表,然后创建本地的工具代理:

private async fetchMCPTools(): Promise<StructuredTool[]> {const response = await this.httpRequest('GET', '/tools');const mcpTools = response.tools;return mcpTools.map((mcpTool: any) => {const schema = this.buildZodSchema(mcpTool.schemaDescription);return new DynamicStructuredTool({name: mcpTool.name,description: mcpTool.description,schema: schema as any,func: async (args: any) => {// 转发给MCP Server执行return await this.executeMCPTool(mcpTool.name, args);},});});
}

这里做了一个重要的架构决定:Agent 端的工具只是 "代理",真正的执行逻辑在 MCP Server 中。注意接入 tools 之后用 zod 做了个 shcema 绑定,可以规定 AI 返回的格式,做 response_format.

第四部分:服务器和前端搭建

WebSocket 服务架构设计

如前面所说,有两个服务端口

  • Agent Server(3001 端口):处理 AI 请求,管理 WebSocket 连接
  • MCP Server(3002 端口):提供工具服务,纯 HTTP API

Agent Server 实现

private async fetchMCPTools(): Promise<StructuredTool[]> {const response = await this.httpRequest('GET', '/tools');const mcpTools = response.tools;return mcpTools.map((mcpTool: any) => {const schema = this.buildZodSchema(mcpTool.schemaDescription);return new DynamicStructuredTool({name: mcpTool.name,description: mcpTool.description,schema: schema as any,func: async (args: any) => {// 转发给MCP Server执行return await this.executeMCPTool(mcpTool.name, args);},});});
}

服务间协作机制

Agent Server 通过 HTTP 请求与 MCP Server 通信:

// Agent 初始化时获取工具列表
const response = await this.httpRequest('GET', '/tools');// Agent 执行工具时调用 MCP Server
const result = await this.httpRequest('POST', '/execute', {toolName: toolName,args: args
});

这种设计让两个服务完全解耦,可以独立部署和扩容。

前端页面搭建

主要包含两个部分:

  1. 左侧:SpreadJS 表格组件
<div class="spreadjs-container"><div id="ss" style="width: 100%; height: 100%;"></div>
</div>

​ 2. 我们用 cdn 去拉去 SpreadJS 的 all 包,这个包在 localhost 域名下是可以运行的,不需要 license, 只是会有水印。具体见项目代码.

​ 3. 右侧:聊天对话窗口

<div class="chat-messages" id="chatMessages"><!-- 消息展示区域 -->
</div><div class="input-area"><input type="text" id="userInput" placeholder="输入你的需求..." /><button id="sendButton">发送</button>
</div>
  1. 这个就是个 chatbox, 怎么实现都可以,拉个三方库也可以.

WebSocket 客户端实现

class SpreadJSAgentClient {constructor() {this.ws = new WebSocket('ws://localhost:3001');this.sessionId = this.generateSessionId();this.connectWebSocket();this.setupSpreadJS();}setupSpreadJS() {// 初始化SpreadJSconst spread = new GC.Spread.Sheets.Workbook(document.getElementById('ss'));// 把一些变量暴露在全局, 方便execute_spreadjs调用window.GC = GC;window.workbook = window.spread = spread;window.sheet = window.activeSheet = spread.getActiveSheet();}handleServerMessage(message) {const { type, data } = message;switch (type) {case 'agent_message':// AI 思考内容this.addAgentMessage(data.content);break;case 'agent_step':// 工具调用过程if (data.output.lastToolCall) {this.displayToolCall(data.output.lastToolCall);}if (data.output.toolResult) {this.displayToolResult(data.output.toolResult);}break;}}
}

SpreadJS 代码执行

当 AI 生成 SpreadJS 代码时,前端会安全地执行这些代码:

executeSpreadJSCode(executionPackage) {try {// Execute the generated codeconst func = new Function('workbook', 'GC', executionPackage.executeCode);func(window.workbook, window.GC);// Run query to get resultsconst queryFunc = new Function('workbook', 'GC', 'return ' + executionPackage.queryCode);const result = queryFunc(window.workbook, window.GC);// Send result back to agentthis.sendExecutionResult(executionPackage.id, result);} catch (error) {console.error('Code execution failed:', error);this.sendExecutionResult(executionPackage.id, { error: error.message });}
}

new Function 还是比较危险的,这只是个教程,不要直接使用.

效果展示

运行 npm run dev 启动三个服务,打开 localhost:3000 访问前端页面,就可以输入需求了. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94606.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94606.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94606.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Word】用 Python 轻松实现 Word 文档对比并生成可视化 HTML 报告

在日常工作和学习中&#xff0c;我们经常需要对两个版本的文档进行比对&#xff0c;比如合同修改、论文修订、报告更新等。手动逐字检查不仅耗时费力&#xff0c;还容易遗漏细节。 今天&#xff0c;我将带你使用 Python python-docx difflib 实现一个自动化 Word 文档对比工具…

从0开始搭建一个前端项目(vue + vite + typescript)

版本 node&#xff1a;v22.17.1 pnpm&#xff1a;v10.13.1 vue&#xff1a;^3.5.18 vite&#xff1a;^7.0.6 typescipt&#xff1a;~5.8.0脚手架初始化vue pnpm create vuelatest只选择&#xff1a; TypeScript, JSX 3. 用vscode打开创建的项目&#xff0c;并删除多余的代码esl…

1.ImGui-环境安装

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 IMGUI是一个被广泛应用到逆向里面的&#xff0c;它可以用来做外部的绘制&#xff0c;比如登录界面&…

基于springboot的二手车交易系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

修改win11任务栏时间字体和小图标颜色

1 打开运行提示框 在桌面按快捷键winR&#xff0c;然后如下图所示输入regedit2 查找路径 1、在路径处粘贴路径计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize 2、如下图所示&#xff0c;双击打开ColorPrevalence&#xff0c;将里面的…

第13集 当您的USB设备不在已实测支持列表,如何让TOS-WLink支持您的USB设备--答案Wireshark USB抓包

问&#xff1a;当您的USB设备不在已实测支持列表&#xff0c;如何让TOS-WLink支持您的USB设备&#xff1f; 答案&#xff1a;使用Wireshark USB抓包&#xff0c;日志发给我 为什么要抓包&#xff1a; USB设备种类繁多&#xff1b;TOS-WLink是单片机&#xff0c;内存紧张&#…

[灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU之比较器

作为刚接触微控制器的初学者&#xff0c;在看到MM32SPIN0280用户手册中“比较器”相关内容时&#xff0c;是不是会感到困惑&#xff1f;比如“5个通用比较器”“轮询功能”“迟滞电压”这些术语&#xff0c;好像都和电机控制有关&#xff0c;但又不知道具体怎么用。别担心&…

⸢ 贰 ⸥ ⤳ 安全架构:数字银行安全体系规划

&#x1f44d;点「赞」&#x1f4cc;收「藏」&#x1f440;关「注」&#x1f4ac;评「论」 &#x1f525;更多文章戳&#x1f449;Whoami&#xff01;-CSDN博客&#x1f680; 在金融科技深度融合的背景下&#xff0c;信息安全已从单纯的技术攻防扩展至架构、合规、流程与创新的…

布隆过滤器完全指南:从原理到实战

布隆过滤器完全指南:从原理到实战 摘要:本文深入解析布隆过滤器的核心原理、实现细节和实际应用,提供完整的Java实现代码,并探讨性能优化策略。适合想要深入理解概率数据结构的开发者阅读。 前言 在大数据时代,如何快速判断一个元素是否存在于海量数据集合中?传统的Hash…

​嵌入式Linux学习 - 网络服务器实现与客户端的通信

1.单循环服务器 2.并发服务器 1. 设置socket属性 2. 进程 ​3. 线程 3.多路IO复用模型 - 提高并发程度 1. 区别 2. IO处理模型 1. 阻塞IO模型 2. 非阻塞IO模型 3. 信号驱动IO 4. IO多路复用 3. 特点 4. 函数接口 1. select 2. poll 3. epoll 半包 1.单循环服务…

Mybatis中缓存机制的理解以及优缺点

文章目录一、MyBatis 缓存机制详解1. 一级缓存&#xff08;Local Cache&#xff09;2. 二级缓存&#xff08;Global Cache&#xff09;3. 缓存执行顺序二、MyBatis 缓存的优点三、MyBatis 缓存的缺点四、适用场景与最佳实践总结MyBatis 提供了完善的缓存机制&#xff0c;用于减…

Rust 登堂 之 类型转换(三)

Rust 是类型安全的语言&#xff0c;因此在Rust 中做类型转换不是一件简单的事&#xff0c;这一章节&#xff0c;我们将对Rust 中的类型转换进行详尽讲解。 高能预警&#xff0c;本章节有些难&#xff0c;可以考虑学了进阶后回头再看 as 转换 先来看一段代码 fn main() {let a…

【MySQL 为什么默认会给 id 建索引? MySQL 主键索引 = 聚簇索引?】

MySQL 索引 MySQL 为什么默认会给 id 建索引&#xff1f; & MySQL 主键索引 聚簇索引&#xff1f; 结论&#xff1a;在 MySQL (InnoDB) 中&#xff0c;主键索引是自动创建的聚簇索引&#xff0c;不需要删除&#xff0c;其他索引是补充优化。 1. MySQL 的id 索引是怎么来的…

[光学原理与应用-321]:皮秒深紫外激光器产品不同阶段使用的工具软件、对应的输出文件

在皮秒深紫外激光器的开发过程中&#xff0c;不同阶段使用的工具软件及其对应的输出文件如下&#xff1a;一、设计阶段工具软件&#xff1a;Zemax OpticStudio&#xff1a;用于光学系统的初步设计和仿真&#xff0c;包括光线追迹、像差分析、优化设计等。MATLAB&#xff1a;用于…

openEuler常用操作指令

openEuler常用操作指令 一、前言 1.简介 openEuler是由开放原子开源基金会孵化的全场景开源操作系统项目&#xff0c;面向数字基础设施四大核心场景&#xff08;服务器、云计算、边缘计算、嵌入式&#xff09;&#xff0c;全面支持ARM、x86、RISC-V、loongArch、PowerPC、SW…

Python爬虫实战:构建网易云音乐个性化音乐播放列表同步系统

1. 引言 1.1 研究背景 在数字音乐生态中,各大音乐平台凭借独家版权、个性化推荐等优势占据不同市场份额。根据国际唱片业协会(IFPI)2024 年报告,全球流媒体音乐用户已突破 50 亿,其中超过 60% 的用户同时使用 2 个及以上音乐平台。用户在不同平台积累的播放列表包含大量…

vscode 配置 + androidStudio配置

插件代码片段 饿了么 icon{"Print to console": {"prefix": "ii-ep-","body": ["i-ep-"],"description": "elementPlus Icon"} }Ts 初始化模版{"Print to console": {"prefix": &q…

DQN(深度Q网络):深度强化学习的里程碑式突破

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; ✨ 1. DQN概述&#xff1a;当深度学习遇见强化学习 DQN&#xff08;D…

个人博客运行3个月记录

个人博客 自推一波&#xff0c;目前我的Hexo个人博客已经优化的足够好了&#xff0c; 已经足够稳定的和简单进行发布和管理&#xff0c;但还是有不少问题&#xff0c;总之先记下来再说 先总结下 关于评论系统方面&#xff0c;我从Waline (快速上手 | Waline) 更换成了&#x…

C89标准关键字以及运算符分类汇总

开发单片机项目学好C语言尤其重要&#xff0c;我感觉学习C语言需要先学好关键字和运算符&#xff0c;我对C语言的关键字和运算符做一下汇总。一、关键字&#xff1a;&#xff08;C89标准一共有32个关键字&#xff09;(1) 数据类型关键字&#xff08;一共12个&#xff0c;分为基…