B端系统自动化MCP工具开发指南
由于 Trae 和 Cursor 主要作为辅助编码工具(类似智能 IDE 或 Code Copilot),而不是运行时框架,我们将把重点放在如何利用它们的开发效率来快速实现核心的 AI 驱动的自动化逻辑。
在这个项目中,我们选择 Playwright 作为浏览器自动化技术,因为它比 Puppeteer 具有更好的跨浏览器支持和内置的 auto-wait 功能,这对于应对 B 端系统复杂的动态页面更加稳定。

B端系统自动化MCP工具技术架构图
架构分层说明
1. 用户交互层
- 最终用户:通过自然语言与AI助手交互
- MCP客户端:Claude Desktop等支持MCP的应用
- 授权机制:关键的安全屏障,确保用户对每次操作有完全控制权
2. MCP服务器层 - 业务逻辑核心
src/server.js
├── 工具路由分发:根据请求类型路由到相应工具
├── 登录管理:系统认证状态维护
├── 数据操作:单条/批量新增的业务逻辑
├── 会话管理:浏览器会话生命周期管理
└── 错误处理:统一的异常处理机制
3. 浏览器自动化层 - 技术实现核心
浏览器引擎 (Playwright)
├── 浏览器控制器:Chromium实例管理
├── 智能表单填充器:字段识别与填充策略
│ ├── 选择器匹配:智能定位表单元素
│ ├── 类型识别:输入框/下拉框/单选框识别
│ └── 填充策略:基于字段类型的值填充逻辑
└── 认证管理器:登录流程自动化├── 凭证安全:敏感信息处理└── 状态验证:登录成功检测
4. 目标系统层 - 外部依赖
- B端系统接口:标准的Web应用架构
- 数据流:从表单提交到数据持久化的完整链路
- 响应处理:成功/错误状态的标准处理
5. 配置与数据流层 - 可维护性保障
配置管理
├── 客户端配置:MCP服务器注册信息
├── 系统配置:浏览器参数、超时设置
├── 监控日志:操作审计和性能监控
└── 错误恢复:重试机制和故障转移
关键技术特性
🛡️ 安全设计
- 用户操作前授权确认
- 凭证信息的安全传递
- 操作范围的最小权限原则
🔧 智能适配
- 动态表单元素识别
- 多选择器回退机制
- 自适应等待策略
📊 可观测性
- 完整的操作日志
- 性能指标监控
- 错误追踪和报告
🔄 容错处理
- 网络异常重试
- 元素查找超时处理
- 操作失败回滚机制
这个架构确保了工具的可扩展性、安全性和稳定性,为B端系统自动化提供了坚实的技术基础。
🤖 从 0 到 1:基于 Node.js 和 Playwright 开发智能体 MCP 工具
🎯 项目目标与技术选型
业务背景: 自动化处理非结构化数据源(例如邮件、聊天记录、外部网站数据)到 B 端系统(如内部 CRM 或 ERP)的条目新增工作,模拟人工操作,解决系统无 API 接口的问题。
核心技术栈:
| 模块 | 技术/工具 | 目的 |
|---|---|---|
| 后端框架 | Node.js (Express) | 构建高性能的 API 入口和任务管理服务。 |
| 浏览器自动化 | Playwright | 模拟人工登录、导航、输入、点击等复杂 UI 交互。 |
| AI 智能层 | OpenAI / Gemini API | 核心数据抽取,动态生成 Playwright 脚本。 |
| 异步处理 | BullMQ (基于 Redis) | 隔离耗时的浏览器和 AI 任务,确保系统高并发和稳定性。 |
| 开发工具 | Trae / Cursor | 加速代码生成、调试和重构,特别是复杂的自动化脚本。 |
🏗️ 第一步:项目初始化与依赖安装
# 创建项目文件夹
mkdir ai-browser-mcp
cd ai-browser-mcp# 初始化 Node.js 项目
npm init -y# 安装核心依赖
npm install express dotenv @bull-mq ioredis playwright openai# 安装 Playwright 浏览器驱动
npx playwright install
🧠 第二步:构建 AI 驱动的“脚本生成器”
传统浏览器自动化的最大痛点是 Selector Fragility(选择器脆弱性)。页面 UI 一旦改变,脚本就会失效。我们的 AI-MCP 核心在于利用 LLM 的能力,根据任务需求和系统描述,动态生成或修复 Playwright 脚本片段。
1. 定义 AI 任务和 Schema
我们将 AI 分为两层:数据抽取(结构化数据)和行为生成(生成可执行代码)。
// src/services/aiSchema.js
// B 端系统条目新增的通用数据结构
const LEAD_SCHEMA = {type: "object",properties: {// ... 结构与上一个回答类似,用于数据抽取}
};// Playwright 脚本生成指令的上下文
const SCRIPT_PROMPT = `你是一名高级 Playwright 自动化工程师。任务:将抽取到的 JSON 数据 {DATA} 输入到目标 B 端系统。目标页面URL: {URL}。B端系统操作步骤描述: {DESCRIPTION}请生成一个异步函数 'executeAutomation(page, data)' 的 JavaScript 代码片段,其中包含登录、导航、以及使用 'page.fill()' 和 'page.click()' 的全部步骤。不要包含 try/catch 或其他辅助函数,只返回核心的异步代码块。
`;
2. 实现 AI 脚本生成器
利用 LLM 的代码生成能力:
// src/services/aiScriptGenerator.js
const { OpenAI } = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });async function generatePlaywrightScript(data, url, description) {const prompt = SCRIPT_PROMPT.replace("{DATA}", JSON.stringify(data, null, 2)).replace("{URL}", url).replace("{DESCRIPTION}", description);try {const completion = await openai.chat.completions.create({model: "gpt-4-turbo-preview", // 选用代码生成能力强的模型messages: [{ role: "system", content: "你是一个 Playwright 脚本生成器,只输出 JavaScript 代码,无需解释。" },{ role: "user", content: prompt }],temperature: 0.1 // 降低随机性,提高稳定性});let script = completion.choices[0].message.content;// 清理代码块标记,只返回纯代码script = script.replace(/```javascript\n|```/g, '').trim();// 验证脚本是否包含预期的函数签名 (Trae/Cursor 可以帮助快速编写这个验证逻辑)if (!script.startsWith('async function executeAutomation(page, data)')) {throw new Error("AI 返回的脚本格式不正确。");}return script;} catch (error) {console.error("AI 脚本生成失败:", error);throw new Error("无法生成有效的自动化脚本。");}
}module.exports = { generatePlaywrightScript };
⏳ 第三步:异步任务与 Playwright 执行器
浏览器自动化是典型的 I/O 密集型且耗时的任务,必须使用异步队列和 Worker 来处理。
1. 队列设置 (Queue Setup)
设置 BullMQ 队列和 Redis 连接,与上一个回答类似。
2. Playwright Worker 执行器
这是 MCP 逻辑的核心:动态加载并执行 AI 生成的代码。
// src/workers/automationWorker.js
const { Worker, Job } = require('bullmq');
const { connection } = require('../queue/queueSetup');
const playwright = require('playwright');
const { generatePlaywrightScript } = require('../services/aiScriptGenerator');
const { extractLeadData } = require('../services/aiExtractor'); // 沿用上一回答的数据抽取服务const BIZ_SYSTEM_URL = 'http://b-system.example.com/login'; // 示例 B 端系统地址const worker = new Worker('automationQueue', async (job) => {const { rawInput, source, systemDescription } = job.data;// 1. AI 抽取数据const extractedData = await extractLeadData(rawInput, source);// 2. AI 生成 Playwright 脚本 (根据抽取到的数据和系统描述)const scriptCode = await generatePlaywrightScript(extractedData, BIZ_SYSTEM_URL, systemDescription);let browser;try {// 3. 启动 Playwright 浏览器browser = await playwright.chromium.launch({ headless: true, // 生产环境使用无头模式timeout: 60000 // 60秒启动超时});const page = await browser.newPage();// 4. 动态执行 AI 生成的脚本// 注意:eval/new Function 存在安全风险,生产环境需确保 AI 输出的隔离和验证const fn = new Function('page', 'data', scriptCode);// 调用执行函数,传入 page 实例和抽取到的数据await fn(page, extractedData); // 5. 可选:截图作为执行成功的凭证const screenshotPath = `screenshots/success_${job.id}.png`;await page.screenshot({ path: screenshotPath });console.log(`[Success] 任务 ${job.id} 成功完成,截图已保存。`);return { status: 'success', screenshot: screenshotPath, entryData: extractedData };} catch (error) {console.error(`[Failure] 任务 ${job.id} 自动化失败:`, error.message);// 失败时截图,帮助调试if (browser) {const errorScreenshotPath = `screenshots/fail_${job.id}.png`;const page = (await browser.pages())[0];await page.screenshot({ path: errorScreenshotPath });return { status: 'failed', error: error.message, screenshot: errorScreenshotPath };}throw error; // 重新抛出错误,让 BullMQ 记录失败} finally {if (browser) {await browser.close();}}
}, { connection });console.log("Playwright Automation Worker 已启动...");
🌐 第四步:Express API 入口
提供一个 API 接口用于接收任务。
// src/server.js (部分更新)
// ... (引入 express, dotenv, setupQueue)app.post('/api/submit-automation-task', async (req, res) => {const { raw_input, // 原始输入 (如邮件正文)source, // 来源 (如 'Customer Email')system_desc // **关键:B端系统的操作描述**} = req.body;if (!raw_input || !system_desc) {return res.status(400).send({ message: "缺少输入或 B 端系统操作描述。" });}try {// 将任务推入 Playwright 自动化队列const job = await addAutomationTask({ rawInput: raw_input, source, systemDescription: system_desc });res.status(202).send({ message: 'AI 自动化任务已接收,正在后台执行。',jobId: job.id});} catch (error) {console.error('API 提交任务失败:', error);res.status(500).send({ message: '内部服务器错误,任务未能进入队列。' });}
});
🚀 第五步:使用 Trae/Cursor 加速开发与调试
1. 代码生成与优化 (Trae/Cursor)
当使用 Trae 或 Cursor 时,你可以利用其 AI 辅助能力来处理复杂的、重复性的代码:
- BullMQ 模板: 快速生成
queueSetup.js和automationWorker.js的基本结构,包括 Redis 连接和 Worker 监听逻辑。 - Playwright 脚本生成器的迭代: 当你发现 AI 生成的 Playwright 脚本不够稳定时,可以直接在 Cursor 中圈选
aiScriptGenerator.js的代码,并要求 AI “根据 Playwright 的最佳实践,添加更健壮的选择器(如page.getByLabel或page.getByRole)”。
2. 调试与修复
浏览器自动化调试是耗时的工作。
-
隔离模式: 在 Cursor 中,你可以快速将
automationWorker.js中的逻辑抽取到一个独立的测试文件中,并运行 “非 Headless” 模式:// 在本地调试时将 headless 设为 false browser = await playwright.chromium.launch({ headless: false, slowMo: 50 // 慢动作有助于观察 }); -
AI 辅助修复: 当脚本执行失败并返回截图时,你可以将错误截图和失败时的上下文日志粘贴给 Trae/Cursor,让它分析并修复
generatePlaywrightScript中使用的基础选择器模板或登录逻辑。例如:“我发现 B 端系统的登录按钮类名变了,请更新生成器中的登录步骤。”
总结与下一步
这个 AI-MCP 工具是一个强大的结合体:
- AI 驱动的数据抽取:将非结构化文本转化为规范 JSON。
- AI 驱动的行为生成:动态创建(或修复)浏览器自动化脚本。
- Playwright 的可靠执行:稳定模拟用户交互。
- 异步队列的高效性:隔离耗时操作,确保 MCP 接口高可用。
B端系统自动化MCP工具开发指南
项目概述
我们将开发一个基于浏览器自动化技术的MCP工具,用于自动在B端系统中新增数据条目。这个工具将模拟用户在浏览器中的操作,实现系统登录、导航到目标页面、填写表单和提交数据。
技术选型
- MCP SDK:
@modelcontextprotocol/sdk-node - 浏览器自动化:
playwright(比Puppeteer更强大) - 运行环境: Node.js 18+
第一步:项目初始化
1.1 创建项目目录结构
mkdir b-system-automation-mcp
cd b-system-automation-mcp
npm init -y
1.2 安装依赖
# 安装MCP SDK
npm install @modelcontextprotocol/sdk-node# 安装浏览器自动化工具
npm install playwright# 安装类型定义(如果用TypeScript)
npm install --save-dev @types/node typescript
1.3 项目结构
b-system-automation-mcp/
├── src/
│ ├── server.js # MCP服务器主文件
│ ├── browser/ # 浏览器自动化模块
│ │ ├── browser.js # 浏览器控制
│ │ ├── auth.js # 认证处理
│ │ └── form-filler.js # 表单填充
│ └── config/
│ └── default.json # 配置文件
├── package.json
└── README.md
第二步:核心代码实现
2.1 MCP服务器主文件 (src/server.js)
const { Server } = require('@modelcontextprotocol/sdk-node/server.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk-node/stdio.js');
const {CallToolRequestSchema,ToolSchema,ListToolsRequestSchema,
} = require('@modelcontextprotocol/sdk-node/types.js');// 导入浏览器自动化模块
const { BrowserAutomation } = require('./browser/browser.js');class BSystemAutomationServer {constructor() {this.server = new Server({name: 'b-system-automation',version: '1.0.0',},{capabilities: {tools: {},},});this.browser = new BrowserAutomation();this.isLoggedIn = false;this.setupToolHandlers();}setupToolHandlers() {// 提供工具列表this.server.setRequestHandler(ListToolsRequestSchema, async () => {return {tools: [{name: 'login_to_system',description: '登录到B端管理系统',inputSchema: {type: 'object',properties: {systemUrl: {type: 'string',description: '系统登录地址'},username: {type: 'string',description: '用户名'},password: {type: 'string',description: '密码'},usernameSelector: {type: 'string',description: '用户名输入框CSS选择器,默认: input[type="text"], input[name*="user"]',default: 'input[type="text"], input[name*="user"]'},passwordSelector: {type: 'string',description: '密码输入框CSS选择器,默认: input[type="password"]',default: 'input[type="password"]'},submitSelector: {type: 'string',description: '提交按钮CSS选择器,默认: button[type="submit"], input[type="submit"]',default: 'button[type="submit"], input[type="submit"]'}},required: ['systemUrl', 'username', 'password']}},{name: 'add_data_entry',description: '在B端系统中新增数据条目',inputSchema: {type: 'object',properties: {targetUrl: {type: 'string',description: '数据新增页面的URL'},formData: {type: 'object',description: '要填写的表单数据,键值对形式',additionalProperties: {type: 'string'}},formSelectors: {type: 'object',description: '表单元素选择器映射',properties: {submitButton: {type: 'string',description: '提交按钮选择器',default: 'button[type="submit"], input[type="submit"]'},successIndicator: {type: 'string',description: '成功提示元素选择器',default: '.success, .alert-success'}}}},required: ['targetUrl', 'formData']}},{name: 'batch_add_entries',description: '批量新增多个数据条目',inputSchema: {type: 'object',properties: {targetUrl: {type: 'string',description: '数据新增页面的URL'},entries: {type: 'array',description: '要新增的条目数组',items: {type: 'object',additionalProperties: {type: 'string'}}},delayBetweenEntries: {type: 'number',description: '条目之间的延迟(毫秒)',default: 1000}},required: ['targetUrl', 'entries']}},{name: 'close_browser',description: '关闭浏览器实例,释放资源',inputSchema: {type: 'object',properties: {}}}],};});// 处理工具调用this.server.setRequestHandler(CallToolRequestSchema, async (request) => {const { name, arguments: args } = request.params;try {switch (name) {case 'login_to_system':return await this.handleLogin(args);case 'add_data_entry':return await this.handleAddEntry(args);case 'batch_add_entries':return await this.handleBatchAdd(args);case 'close_browser':return await this.handleCloseBrowser();default:throw new Error(`未知的工具: ${name}`);}} catch (error) {return {content: [{type: 'text',text: `操作失败: ${error.message}`}],isError: true};}});}async handleLogin(args) {const {systemUrl,username,password,usernameSelector = 'input[type="text"], input[name*="user"]',passwordSelector = 'input[type="password"]',submitSelector = 'button[type="submit"], input[type="submit"]'} = args;await this.browser.launch();const result = await this.browser.login(systemUrl,username,password,{ usernameSelector, passwordSelector, submitSelector });this.isLoggedIn = result.success;return {content: [{type: 'text',text: result.success ? `登录成功: ${result.message}`: `登录失败: ${result.message}`}]};}async handleAddEntry(args) {if (!this.isLoggedIn) {throw new Error('请先登录系统');}const { targetUrl, formData, formSelectors = {} } = args;const result = await this.browser.fillForm(targetUrl, formData, formSelectors);return {content: [{type: 'text',text: result.success? `数据新增成功: ${result.message}`: `数据新增失败: ${result.message}`}]};}async handleBatchAdd(args) {if (!this.isLoggedIn) {throw new Error('请先登录系统');}const { targetUrl, entries, delayBetweenEntries = 1000 } = args;const results = [];for (let i = 0; i < entries.length; i++) {const entry = entries[i];const result = await this.browser.fillForm(targetUrl, entry);results.push({index: i + 1,success: result.success,message: result.message});// 添加延迟,避免操作过快被系统检测if (i < entries.length - 1) {await new Promise(resolve => setTimeout(resolve, delayBetweenEntries));}}const successCount = results.filter(r => r.success).length;return {content: [{type: 'text',text: `批量操作完成: 成功 ${successCount}/${entries.length} 条`},{type: 'text',text: `详细结果: ${JSON.stringify(results, null, 2)}`}]};}async handleCloseBrowser() {await this.browser.close();this.isLoggedIn = false;return {content: [{type: 'text',text: '浏览器已关闭,资源已释放'}]};}async connect() {const transport = new StdioServerTransport();await this.server.connect(transport);console.error('B端系统自动化MCP服务器已启动');}
}// 启动服务器
async function main() {const server = new BSystemAutomationServer();await server.connect();
}main().catch(console.error);
2.2 浏览器自动化模块 (src/browser/browser.js)
const { chromium } = require('playwright');class BrowserAutomation {constructor() {this.browser = null;this.context = null;this.page = null;this.isLaunched = false;}async launch() {if (this.isLaunched) {return;}this.browser = await chromium.launch({ headless: false, // 设置为true可无头运行slowMo: 100 // 操作延迟,便于观察});this.context = await this.browser.newContext({viewport: { width: 1280, height: 720 },userAgent: 'MCP-Bot/1.0'});this.page = await this.context.newPage();this.isLaunched = true;console.error('浏览器已启动');}async login(systemUrl, username, password, selectors = {}) {if (!this.isLaunched) {throw new Error('浏览器未启动');}try {await this.page.goto(systemUrl, { waitUntil: 'networkidle' });// 等待页面加载await this.page.waitForTimeout(2000);// 填写用户名await this.page.click(selectors.usernameSelector);await this.page.fill(selectors.usernameSelector, username);// 填写密码await this.page.click(selectors.passwordSelector);await this.page.fill(selectors.passwordSelector, password);// 点击登录await this.page.click(selectors.submitSelector);// 等待登录完成await this.page.waitForTimeout(3000);// 检查是否登录成功(根据URL变化或页面元素)const currentUrl = this.page.url();if (currentUrl.includes('dashboard') || currentUrl !== systemUrl) {return { success: true, message: '登录成功' };} else {// 检查是否有错误提示const errorElement = await this.page.$('.error, .alert-danger');if (errorElement) {const errorText = await errorElement.textContent();return { success: false, message: errorText };}return { success: false, message: '登录状态未知' };}} catch (error) {return { success: false, message: error.message };}}async fillForm(targetUrl, formData, selectors = {}) {if (!this.isLaunched) {throw new Error('浏览器未启动');}try {await this.page.goto(targetUrl, { waitUntil: 'networkidle' });await this.page.waitForTimeout(2000);// 智能填写表单for (const [fieldName, fieldValue] of Object.entries(formData)) {await this.intelligentFormFill(fieldName, fieldValue);}// 提交表单const submitSelector = selectors.submitButton || 'button[type="submit"], input[type="submit"]';await this.page.click(submitSelector);await this.page.waitForTimeout(3000);// 检查提交结果const successSelector = selectors.successIndicator || '.success, .alert-success';const successElement = await this.page.$(successSelector);if (successElement) {return { success: true, message: '数据新增成功' };} else {// 检查错误const errorElement = await this.page.$('.error, .alert-danger');if (errorElement) {const errorText = await errorElement.textContent();return { success: false, message: errorText };}return { success: true, message: '操作完成(成功状态未检测)' };}} catch (error) {return { success: false, message: error.message };}}async intelligentFormFill(fieldName, value) {// 智能匹配表单字段const possibleSelectors = [`input[name*="${fieldName.toLowerCase()}"]`,`textarea[name*="${fieldName.toLowerCase()}"]`,`[placeholder*="${fieldName}"]`,`input[type="text"]:nth-of-type(${this.getFieldIndex(fieldName)})`];for (const selector of possibleSelectors) {try {const element = await this.page.$(selector);if (element) {await element.click();await element.fill(value);return true;}} catch (error) {// 继续尝试下一个选择器continue;}}// 如果智能匹配失败,尝试手动输入到焦点元素await this.page.keyboard.type(value);return false;}getFieldIndex(fieldName) {// 根据常见字段名返回可能的索引const commonFields = {'name': 1, 'title': 1, 'username': 1,'description': 2, 'content': 2,'email': 3, 'phone': 4, 'address': 5};return commonFields[fieldName.toLowerCase()] || 1;}async close() {if (this.browser) {await this.browser.close();this.isLaunched = false;console.error('浏览器已关闭');}}
}module.exports = { BrowserAutomation };
第三步:配置MCP客户端
3.1 创建Claude Desktop配置文件
在Claude配置目录创建 claude_desktop_config.json:
{"mcpServers": {"b-system-automation": {"command": "node","args": ["/绝对路径/到/你的/项目/src/server.js"]}}
}
3.2 配置说明
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
第四步:使用示例
4.1 基本使用流程
// 通过MCP客户端调用工具的示例参数
const loginParams = {systemUrl: "https://your-b-system.com/login",username: "your_username",password: "your_password",usernameSelector: "input[name='username']",passwordSelector: "input[name='password']",submitSelector: "button.login-btn"
};const addEntryParams = {targetUrl: "https://your-b-system.com/data/add",formData: {name: "测试产品",description: "这是一个测试产品描述",category: "电子产品",price: "299.99",stock: "100"},formSelectors: {submitButton: "button[type='submit']",successIndicator: ".alert-success"}
};
4.2 在Cursor/Trae中的使用
在支持MCP的AI客户端中,你可以这样使用:
"请调用b-system-automation工具,帮我登录到管理系统并新增一个产品条目。"
系统会自动调用相应的工具并请求你的授权。
第五步:高级功能与优化
5.1 错误处理与重试机制
// 在browser.js中添加重试逻辑
async function withRetry(operation, maxRetries = 3) {for (let attempt = 1; attempt <= maxRetries; attempt++) {try {return await operation();} catch (error) {if (attempt === maxRetries) throw error;await this.page.waitForTimeout(1000 * attempt);}}
}
5.2 配置文件支持
创建 src/config/default.json:
{"browser": {"headless": false,"slowMo": 100,"timeout": 30000},"selectors": {"username": "input[type='text']","password": "input[type='password']","submit": "button[type='submit']"}
}
5.3 安全考虑
- 敏感信息处理: 不要硬编码密码,使用环境变量
- 会话管理: 实现自动重新登录机制
- 速率限制: 避免操作过快触发反爬虫机制
第六步:测试与调试
6.1 本地测试脚本
创建 test.js 进行本地测试:
const { BrowserAutomation } = require('./src/browser/browser.js');async function test() {const browser = new BrowserAutomation();try {await browser.launch();// 测试登录const loginResult = await browser.login('https://example.com/login','testuser','testpass');console.log('Login result:', loginResult);} finally {await browser.close();}
}test();
6.2 调试技巧
- 设置
headless: false观察浏览器操作 - 使用
slowMo参数减慢操作速度 - 添加详细的日志记录
项目总结
这个MCP工具提供了完整的B端系统自动化能力,主要特点:
- 模块化设计: 浏览器自动化与MCP协议分离
- 智能表单填充: 自动匹配表单字段
- 批量操作支持: 支持大量数据自动录入
- 错误处理: 完善的异常处理和重试机制
- 可扩展性: 易于添加新的系统适配
通过这个工具,你可以实现各种B端系统的自动化数据管理,大大提升工作效率。记得根据实际目标系统调整选择器和操作逻辑。
