實戰筆記——構建智能Agent:SpreadJS代碼助手

目錄

前言

解決思路

需求理解

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;分為基…