实战!前端开发完整 LangChain AI 智能体(附源码)

1 阅读6分钟

前置知识

在开始之前,我们需要掌握之前所学的核心技能:

  • LangChain 基础:ChatOpenAI、PromptTemplate 基本用法
  • Memory 机制:对话记忆的原理与实现
  • Tool 封装:自定义工具的开发规范
  • Agent 基础:ReAct 模式的理解
  • LangGraph 入门:节点、边、状态的基本概念

如果对以上任一知识点还不熟悉,可以查看我之前相关的几篇文章。

项目整体架构设计

项目概述

本次实战项目是一个完整的前端开发助手智能体,具备以下能力:

  • ✅ 多轮对话记忆(跨会话持久化)
  • ✅ 多种前端工具调用(代码格式化、文件读取、数据转换)
  • ✅ 自主决策与任务规划(ReAct 模式)
  • ✅ 可观测的执行流程(LangGraph 状态追踪)

整体架构图

graph TD
    A[用户输入] --> B[LangGraph 工作流]

    subgraph B [LangGraph 工作流]
        C[agent<br/>推理] --> D[tools<br/>执行]
        D --> E[should_continue<br/>条件路由]
        E -->|有工具调用| C
    end

    B --> G[核心能力层]

    subgraph G [核心能力层]
        H[持久化记忆<br/>JSON存储] -.-
        I[工具集<br/>3个业务工具] -.-
        J[基础模型<br/>阿里云百炼] -.-
        K[状态管理<br/>LangGraph]
    end

模块划分

模块文件职责
配置模块config.ts环境变量、模型初始化
记忆模块memory.ts对话历史的持久化存储与加载
工具模块tools/前端专用工具集(代码格式化、文件读取、数据转换)
工作流模块workflow.tsLangGraph 状态图定义与编译
入口模块index.ts交互式命令行界面

第一步:项目初始化与环境配置

1.1 创建项目

# 创建项目文件夹
mkdir ai-frontend-assistant
cd ai-frontend-assistant

# 初始化 npm 项目
npm init -y

# 安装依赖
npm install @langchain/openai @langchain/core @langchain/langgraph dotenv zod
npm install -D typescript @types/node tsx

# 创建源代码目录
mkdir src src/tools

1.2 环境变量配置

创建 .env 文件:

# .env
DASHSCOPE_API_KEY=你的API Key
DASHSCOPE_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

# 记忆文件存储路径
MEMORY_FILE_PATH=./memory.json

# 最大迭代次数
MAX_AGENT_ITERATIONS=5

1.3 TypeScript 配置

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

第二步:核心功能模块实现

2.1 配置模块

// src/config.ts
import { ChatOpenAI } from "@langchain/openai";
import dotenv from "dotenv";

dotenv.config();

// 模型配置
export const MODEL_CONFIG = {
  apiKey: process.env.DASHSCOPE_API_KEY!,
  baseURL: process.env.DASHSCOPE_API_URL!,
  model: "qwen-plus",           // 使用 plus 版本,平衡性能和成本
  temperature: 0.3,             // 低温度,提高决策稳定性
  maxTokens: 2048,
};

// 创建模型实例
export function createModel() {
  return new ChatOpenAI({
    apiKey: process.env.DASHSCOPE_API_KEY,
    configuration: {
      baseURL: process.env.DASHSCOPE_API_URL,
    },
    model: MODEL_CONFIG.model,
    temperature: MODEL_CONFIG.temperature,
    maxTokens: MODEL_CONFIG.maxTokens,
  });
}

// Agent 配置
export const AGENT_CONFIG = {
  maxIterations: parseInt(process.env.MAX_AGENT_ITERATIONS || "5"),
  systemPrompt: `你是一个专业的前端开发助手,具备以下能力:
1. 代码格式化:使用 code_formatter 工具
2. 文件读取:使用 file_reader 工具
3. 数据转换:使用 data_converter 工具

请遵循 ReAct 模式:
- 先分析用户需求,决定是否需要调用工具
- 需要时调用合适的工具,等待结果后再继续
- 完成任务后用友好的方式回复用户

注意:最多调用 5 次工具,避免无限循环。`,
};

// 记忆配置
export const MEMORY_CONFIG = {
  filePath: process.env.MEMORY_FILE_PATH || "./memory.json",
  maxHistoryTokens: 4000,       // 最大历史 tokens
  maxMessages: 20,              // 最大消息条数
};

2.2 记忆模块(持久化)

// src/memory.ts
import { BaseMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
import fs from "fs/promises";
import path from "path";
import { MEMORY_CONFIG } from "./config";

// 会话状态接口
export interface SessionState {
  sessionId: string;
  messages: BaseMessage[];
  createdAt: number;
  updatedAt: number;
  metadata?: Record<string, any>;
}

// 记忆管理器类
export class MemoryManager {
  private sessions: Map<string, SessionState> = new Map();
  private filePath: string;

  constructor(filePath: string = MEMORY_CONFIG.filePath) {
    this.filePath = path.resolve(filePath);
  }

  // 加载持久化数据
  async load(): Promise<void> {
    try {
      const content = await fs.readFile(this.filePath, "utf-8");
      const data = JSON.parse(content);
      
      // 恢复会话数据
      for (const session of data.sessions || []) {
        // 将存储的普通对象转换回 BaseMessage 实例
        const messages = (session.messages || []).map((msg: any) => {
          if (msg.type === "human") {
            return new HumanMessage(msg.content);
          }
          return new AIMessage(msg.content);
        });
        
        this.sessions.set(session.sessionId, {
          ...session,
          messages,
        });
      }
      
      console.log(`📂 已加载 ${this.sessions.size} 个会话`);
    } catch (error) {
      // 文件不存在,使用空状态
      console.log("📂 未找到记忆文件,将创建新会话");
    }
  }

  // 保存到持久化存储
  async save(): Promise<void> {
    const data = {
      sessions: Array.from(this.sessions.values()).map(session => ({
        ...session,
        messages: session.messages.map(msg => ({
          type: msg._getType(),
          content: msg.content,
        })),
      })),
      lastUpdated: Date.now(),
    };
    
    await fs.writeFile(this.filePath, JSON.stringify(data, null, 2));
    console.log(`💾 已保存 ${this.sessions.size} 个会话`);
  }

  // 获取或创建会话
  getOrCreateSession(sessionId: string): SessionState {
    if (this.sessions.has(sessionId)) {
      return this.sessions.get(sessionId)!;
    }
    
    const newSession: SessionState = {
      sessionId,
      messages: [],
      createdAt: Date.now(),
      updatedAt: Date.now(),
    };
    
    this.sessions.set(sessionId, newSession);
    return newSession;
  }

  // 添加消息到会话
  addMessage(sessionId: string, message: BaseMessage): void {
    const session = this.getOrCreateSession(sessionId);
    session.messages.push(message);
    session.updatedAt = Date.now();
    
    // 限制消息数量(滑动窗口)
    if (session.messages.length > MEMORY_CONFIG.maxMessages) {
      session.messages = session.messages.slice(-MEMORY_CONFIG.maxMessages);
    }
  }

  // 获取会话历史
  getHistory(sessionId: string): BaseMessage[] {
    const session = this.getOrCreateSession(sessionId);
    return [...session.messages];
  }

  // 清空会话
  async clearSession(sessionId: string): Promise<void> {
    this.sessions.delete(sessionId);
    await this.save();
  }

  // 获取所有会话列表
  listSessions(): { sessionId: string; createdAt: number; updatedAt: number; messageCount: number }[] {
    return Array.from(this.sessions.values()).map(session => ({
      sessionId: session.sessionId,
      createdAt: session.createdAt,
      updatedAt: session.updatedAt,
      messageCount: session.messages.length,
    }));
  }
}

2.3 工具模块实现

工具 1:代码格式化

// src/tools/code-formatter.ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";

// 模拟代码格式化(生产环境可集成 prettier)
function formatJavaScript(code: string, indentSize: number = 2): string {
  const indent = " ".repeat(indentSize);
  
  return code
    .split("\n")
    .map(line => line.trim())
    .map(line => {
      if (line.match(/^[})\]]/)) return line;
      if (line.match(/[({[]$/)) return indent + line;
      return indent + line;
    })
    .join("\n");
}

function formatJSON(code: string, indentSize: number = 2): string {
  try {
    const obj = JSON.parse(code);
    return JSON.stringify(obj, null, indentSize);
  } catch {
    return "错误:无效的 JSON 格式";
  }
}

export const codeFormatter = tool(
  async ({ code, language, indentSize = 2 }) => {
    try {
      if (!code || code.trim().length === 0) {
        return "错误:代码内容不能为空";
      }
      
      let formattedCode: string;
      
      switch (language) {
        case "javascript":
        case "typescript":
          formattedCode = formatJavaScript(code, indentSize);
          break;
        case "json":
          formattedCode = formatJSON(code, indentSize);
          break;
        default:
          return `暂不支持的语言: ${language},支持:javascript, typescript, json`;
      }
      
      return `✅ 代码格式化完成:\n\`\`\`${language}\n${formattedCode}\n\`\`\``;
    } catch (error) {
      return `格式化失败:${error instanceof Error ? error.message : String(error)}`;
    }
  },
  {
    name: "code_formatter",
    description: `格式化前端代码,使代码更美观易读。
使用场景:用户提供未格式化的代码、粘贴的代码排版混乱时。
支持的语言:javascript, typescript, json`,
    schema: z.object({
      code: z.string().describe("需要格式化的原始代码"),
      language: z.enum(["javascript", "typescript", "json"]).describe("代码语言类型"),
      indentSize: z.number().min(2).max(8).default(2).describe("缩进空格数,默认2"),
    }),
  }
);

工具 2:文件读取

// src/tools/file-reader.ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";

export const fileReader = tool(
  async ({ filePath, maxSize = 1024 * 1024 }) => {
    try {
      // 安全检查:防止路径遍历攻击
      const resolvedPath = path.resolve(filePath);
      if (!resolvedPath.startsWith(process.cwd())) {
        return `❌ 错误:无法访问项目目录外的文件:${filePath}`;
      }
      
      // 检查文件是否存在
      const stats = await fs.stat(resolvedPath).catch(() => null);
      if (!stats) {
        return `❌ 错误:文件不存在:${filePath}`;
      }
      
      // 检查文件大小
      if (stats.size > maxSize) {
        return `❌ 错误:文件过大(${(stats.size / 1024).toFixed(2)} KB),超过限制(${maxSize / 1024} KB)`;
      }
      
      // 读取文件
      const content = await fs.readFile(resolvedPath, "utf-8");
      
      // 获取文件扩展名用于语法高亮
      const ext = path.extname(filePath).slice(1) || "text";
      
      return `📄 文件内容(${filePath}):\n\`\`\`${ext}\n${content}\n\`\`\``;
    } catch (error) {
      return `读取文件失败:${error instanceof Error ? error.message : String(error)}`;
    }
  },
  {
    name: "file_reader",
    description: `读取本地文件内容。
使用场景:用户需要查看代码文件、阅读文档、分析配置文件时。
限制:只能读取项目目录内的文件,默认最大 1MB。`,
    schema: z.object({
      filePath: z.string().describe("文件路径,支持相对路径(如 ./src/index.ts)"),
      maxSize: z.number().optional().describe("最大文件大小(字节),默认 1MB"),
    }),
  }
);

工具 3:数据转换

// src/tools/data-converter.ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";

export const dataConverter = tool(
  async ({ input, fromFormat, toFormat }) => {
    try {
      let result: string;
      
      // JSON 转 CSV
      if (fromFormat === "json" && toFormat === "csv") {
        const data = JSON.parse(input);
        
        if (!Array.isArray(data) || data.length === 0) {
          return "错误:JSON 必须是非空数组";
        }
        
        const headers = Object.keys(data[0]);
        const rows = data.map(obj => 
          headers.map(header => {
            const value = obj[header];
            if (value === undefined || value === null) return "";
            if (typeof value === "object") return JSON.stringify(value);
            const str = String(value);
            return str.includes(",") || str.includes('"') 
              ? `"${str.replace(/"/g, '""')}"` 
              : str;
          }).join(",")
        );
        
        result = [headers.join(","), ...rows].join("\n");
        return `JSON 转 CSV 成功:\n\`\`\`csv\n${result}\n\`\`\``;
      }
      
      // CSV 转 JSON
      if (fromFormat === "csv" && toFormat === "json") {
        const lines = input.trim().split("\n");
        const headers = lines[0].split(",").map(h => h.trim().replace(/^"|"$/g, ""));
        
        const records = lines.slice(1).map(line => {
          // 简单的 CSV 解析(生产环境建议使用专业库)
          const values: string[] = [];
          let current = "";
          let inQuotes = false;
          
          for (let i = 0; i < line.length; i++) {
            const char = line[i];
            if (char === '"') {
              inQuotes = !inQuotes;
            } else if (char === "," && !inQuotes) {
              values.push(current.trim().replace(/^"|"$/g, ""));
              current = "";
            } else {
              current += char;
            }
          }
          values.push(current.trim().replace(/^"|"$/g, ""));
          
          return headers.reduce((obj, header, idx) => {
            obj[header] = values[idx] || "";
            return obj;
          }, {} as Record<string, string>);
        });
        
        result = JSON.stringify(records, null, 2);
        return `CSVJSON 成功:\n\`\`\`json\n${result}\n\`\`\``;
      }
      
      return `❌ 不支持的转换类型 ${fromFormat} -> ${toFormat},支持:json->csv 或 csv->json`;
    } catch (error) {
      if (error instanceof SyntaxError) {
        return `错误:JSON 格式解析失败 - ${error.message}`;
      }
      return `转换失败:${error instanceof Error ? error.message : String(error)}`;
    }
  },
  {
    name: "data_converter",
    description: `数据格式转换工具,支持 JSON 和 CSV 之间的互相转换。
使用场景:用户需要将 JSON 数据导出为表格格式,或将 CSV 数据转换为 JSON 结构。`,
    schema: z.object({
      input: z.string().describe("需要转换的原始数据内容"),
      fromFormat: z.enum(["json", "csv"]).describe("源数据格式"),
      toFormat: z.enum(["json", "csv"]).describe("目标数据格式"),
    }),
  }
);

工具统一导出

// src/tools/index.ts
import { codeFormatter } from "./code-formatter";
import { fileReader } from "./file-reader";
import { dataConverter } from "./data-converter";

// 所有工具列表
export const ALL_TOOLS = [codeFormatter, fileReader, dataConverter];

// 工具名称到工具的映射
export const TOOLS_BY_NAME = Object.fromEntries(
  ALL_TOOLS.map(tool => [tool.name, tool])
);

// 按类别获取工具
export const getToolsByCategory = (category: "code" | "file" | "data") => {
  switch (category) {
    case "code":
      return [codeFormatter];
    case "file":
      return [fileReader];
    case "data":
      return [dataConverter];
    default:
      return ALL_TOOLS;
  }
};

2.4 工作流模块(LangGraph 整合)

// src/workflow.ts
import { StateGraph, START, END } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { AIMessage, BaseMessage } from "@langchain/core/messages";
import { createModel, AGENT_CONFIG } from "./config";
import { ALL_TOOLS } from "./tools";
import { MemoryManager } from "./memory";

// 扩展状态类型
type AgentState = {
  messages: BaseMessage[];
  intent?: "technical" | "billing" | "general";
  resolution?: string;
  sessionId: string;
  iterationCount: number;
}

// 创建 Agent 工作流
export async function createAgentWorkflow(memoryManager: MemoryManager) {
  const model = createModel();
  
  // 绑定工具到模型
  const modelWithTools = model.bindTools(ALL_TOOLS);

  // Agent 节点:推理决策
  async function agentNode(state: AgentState) {
    // 注入系统提示词
    const systemMessage = {
      role: "system",
      content: AGENT_CONFIG.systemPrompt,
    };
    
    // 获取历史消息
    const history = memoryManager.getHistory(state.sessionId);
    
    // 构建消息列表
    const messages = [
      systemMessage,
      ...history,
      ...state.messages,
    ];
    
    const response = await modelWithTools.invoke(messages);
    
    // 保存 AI 回复到记忆
    memoryManager.addMessage(state.sessionId, response);
    
    return {
      messages: [response],
      iterationCount: (state.iterationCount || 0) + 1,
    };
  }

  // 工具节点(使用 LangGraph 预置)
  const toolNode = new ToolNode(ALL_TOOLS);

  // 条件路由:决定是否继续循环
  function shouldContinue(state: AgentState) {
    const lastMessage = state.messages[state.messages.length - 1] as AIMessage;
    
    // 检查是否达到最大迭代次数
    if (state.iterationCount >= AGENT_CONFIG.maxIterations) {
      console.log("⚠️ 达到最大迭代次数,强制终止");
      return END;
    }
    
    // 如果模型调用了工具,继续到工具节点
    if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
      return "tools";
    }
    
    // 否则结束
    return END;
  }

  // 构建状态图
  const workflow = new StateGraph<AgentState>({
    channels: {
      messages: { value: (a, b) => [...(a || []), ...(b || [])] },
      sessionId: { value: (_, b) => b },
      iterationCount: { value: (_, b) => b ?? 0 },
    },
  })
    .addNode("agent", agentNode)
    .addNode("tools", toolNode)
    .addEdge(START, "agent")
    .addConditionalEdges("agent", shouldContinue)
    .addEdge("tools", "agent");  // 工具执行完后回到 agent

  return workflow.compile();
}

2.5 主入口与交互界面

// src/index.ts
import { HumanMessage } from "@langchain/core/messages";
import { createAgentWorkflow } from "./workflow";
import { MemoryManager } from "./memory";
import readline from "readline";

// 创建命令行交互界面
function createReadlineInterface() {
  return readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
}

// 打印欢迎信息
function printWelcome() {
  console.log("\n" + "=".repeat(60));
  console.log("   🤖 前端开发助手 AI 智能体");
  console.log("=".repeat(60));
  console.log("  可用工具:");
  console.log("    • 代码格式化 (code_formatter)");
  console.log("    • 文件读取 (file_reader)");
  console.log("    • 数据转换 (data_converter)");
  console.log("  指令:");
  console.log("    • /new    - 开始新会话");
  console.log("    • /list   - 查看会话列表");
  console.log("    • /clear  - 清空当前会话");
  console.log("    • /exit   - 退出程序");
  console.log("=".repeat(60) + "\n");
}

// 主函数
async function main() {
  // 初始化记忆管理器
  const memoryManager = new MemoryManager();
  await memoryManager.load();
  
  // 创建工作流
  const app = await createAgentWorkflow(memoryManager);
  
  // 当前会话 ID
  let currentSessionId = `session_${Date.now()}`;
  
  printWelcome();
  console.log(`📌 当前会话: ${currentSessionId}\n`);
  
  const rl = createReadlineInterface();
  
  // 处理用户输入
  const processInput = async (input: string) => {
    const trimmed = input.trim();
    
    // 处理命令
    if (trimmed === "/exit") {
      await memoryManager.save();
      console.log("\n👋 再见!\n");
      rl.close();
      process.exit(0);
      return;
    }
    
    if (trimmed === "/new") {
      currentSessionId = `session_${Date.now()}`;
      console.log(`\n✨ 已创建新会话: ${currentSessionId}\n`);
      rl.prompt();
      return;
    }
    
    if (trimmed === "/list") {
      const sessions = memoryManager.listSessions();
      console.log("\n📋 会话列表:");
      for (const session of sessions) {
        const marker = session.sessionId === currentSessionId ? "🟢 " : "   ";
        console.log(`${marker}${session.sessionId} (${session.messageCount} 条消息)`);
      }
      console.log("");
      rl.prompt();
      return;
    }
    
    if (trimmed === "/clear") {
      await memoryManager.clearSession(currentSessionId);
      console.log("\n🗑️ 当前会话已清空\n");
      rl.prompt();
      return;
    }
    
    // 空输入处理
    if (trimmed === "") {
      rl.prompt();
      return;
    }
    
    // 正常对话处理
    console.log("\n🤖 AI 思考中...\n");
    const startTime = Date.now();
    
    try {
      // 保存用户消息到记忆
      const userMessage = new HumanMessage(trimmed);
      memoryManager.addMessage(currentSessionId, userMessage);
      
      // 执行工作流
      const result = await app.invoke({
        messages: [userMessage],
        sessionId: currentSessionId,
        iterationCount: 0,
      });
      
      // 获取最终回复
      const lastMessage = result.messages[result.messages.length - 1];
      const responseTime = ((Date.now() - startTime) / 1000).toFixed(1);
      
      console.log(`✅ 回复 (${responseTime}s):\n`);
      console.log(lastMessage.content);
      console.log(`\n📊 迭代次数: ${result.iterationCount}\n`);
    } catch (error) {
      console.error(`\n❌ 错误: ${error}\n`);
    }
    
    rl.prompt();
  };
  
  rl.on("line", processInput);
  rl.prompt();
}

// 优雅退出处理
process.on("SIGINT", async () => {
  console.log("\n\n🔄 正在保存记忆...");
  const memoryManager = new MemoryManager();
  await memoryManager.save();
  console.log("✅ 记忆已保存\n");
  process.exit(0);
});

// 启动
main().catch(console.error);

第三步:功能测试与效果展示

测试用例 1:代码格式化

📝 用户:帮我格式化这段代码 function test(){console.log("hello")}

🤖 AI 思考中...

✅ 回复 (2.3s):

我来帮您格式化这段 JavaScript 代码:

  function test(){
    console.log("hello")
  }

格式化后的代码添加了适当的缩进,结构更清晰了。

📊 迭代次数: 1

测试用例 2:多轮对话记忆

📝 用户:我叫小明,是一名前端开发

🤖 回复:很高兴认识你小明!作为一名前端开发,有什么我可以帮你的吗?

📝 用户:我叫什么名字?

🤖 回复:你刚才说你叫小明。有什么需要帮助的吗?

测试用例 3:复杂任务(多工具调用)

📝 用户:帮我读取当前目录的 package.json,然后把依赖信息转换成 CSV 格式

🤖 AI 思考中...

📊 迭代次数: 3

✅ 回复:

我已经完成了以下操作:
1. 读取了 package.json 文件
2. 提取了 dependencies 信息
3. 转换成了 CSV 格式

依赖信息 CSV:
name,version
@langchain/openai,^0.2.0
@langchain/core,^0.3.0
dotenv,^16.0.0
zod,^3.22.0

项目拓展思路

1. 增加更多工具

// 可拓展的工具方向
- HTTP 请求工具(调用外部 API)
- Git 操作工具(提交、分支管理)
- 数据库查询工具
- 图片处理工具

2. 多 Agent 协作

// 使用 LangGraph 实现多 Agent 协作
const multiAgentGraph = new StateGraph(...)
  .addNode("code_writer", codeWriterAgent)
  .addNode("code_reviewer", codeReviewerAgent)
  .addNode("test_generator", testGeneratorAgent)
  .addEdge("code_writer", "code_reviewer")
  .addConditionalEdges("code_reviewer", (state) => 
    state.passed ? "test_generator" : "code_writer"
  );

3. Human-in-the-loop

// 人工审批节点
async function humanApproval(state) {
  const needApproval = state.requiresApproval;
  if (needApproval) {
    // 中断执行,等待人工输入
    return interrupt("请审批以下操作...");
  }
  return state;
}

开发难点与解决方案

难点解决方案
状态类型定义复杂使用 MessagesAnnotation 预设类型,按需扩展
工具描述不够清晰详细描述使用场景、参数格式、输出示例
Agent 陷入循环设置最大迭代次数,添加循环检测逻辑
记忆持久化格式统一消息序列化格式,支持恢复
工具调用参数错误使用 Zod 严格校验,返回友好错误信息

结语

通过本次实战,我们从零到一构建了一个完整的 AI 智能体。这个项目整合了 LangChain 生态的核心组件:Memory(记忆)、Tool(工具)、Agent(智能体)、LangGraph(工作流)。

对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!