ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序智能机器人学习版源码拆解:从关键词匹配到本地学习

微信小程序智能机器人学习版源码拆解:从关键词匹配到本地学习 简介这是一套面向微信小程序初学者的智能机器人学习版源码适合想了解小程序从项目搭建、页面配置到交互逻辑完整流程的开发者也可作为前端入门或智能问答类小程序的起步模板。资源包采用rar压缩格式整体体积仅15KB共包含19个文件其中以js逻辑脚本、wxml页面结构、wxss样式表为主辅以json配置文件和png/jpg图片素材各类型文件分工明确方便对照学习。目前已有177人学习浏览。借助这份代码读者可以学习app全局配置对页面路径和窗口样式的统一管理理解页面数据绑定、点击跳转与事件响应的实现方式并通过utils工具模块了解请求封装或公共函数组织的常用套路从而快速建立小程序开发的整体认知。整个项目结构简洁图片素材与样式表搭配完整适合在微信开发者工具中直接导入运行、逐行调试也适合作为课程设计、毕业设计或二次开发的学习起点。1. 微信小程序源码的智能机器人学习版先拆开再决定要不要动工“微信小程序源码 智能机器人学习版”听起来像一份打包好的小程序工程但实际动手时我更愿意把它当成一张地图目录里至少要有页面层、逻辑层、数据层三块。学习版的价值不在“多智能”而在把聊天机器人主链路完整串起来——用户发消息、机器人匹配规则、给出回复、没命中就触发学习入口。适合三类人第一次写小程序的前端新人、要做课程设计的大学生、被上级要求“一周出个聊天机器人 demo”的工程师。它解决的最大问题不是算法而是让你有一个能直接导入微信开发者工具、跑得通的最小闭环。2. 先看清源码骨架智能机器人学习版的工程结构与选型理由2.1 为什么学习版选原生小程序而不是 uni-app / Taro很多仓库标题写着 uni-app因为它能多端复用。但“学习版”的首要目标是让人看懂不是让人一套代码四处发版。原生小程序有四个天然优势编译器就是微信开发者工具打开即跑页面路由、事件绑定、生命周期都是微信自己的语法网上资料最多调试时可以直接看 AppData 和 Storage方便观察机器人内部规则后续要接云开发、客服消息、订阅消息原生 API 路径最短。如果你已经熟练 React/Vue用 uni-app 确实更顺手。但当你拿到一份“智能机器人学习版源码”时我建议先看app.json和pages目录。如果发现用了第三方框架第一件事是找main.js或main.ts映射回小程序页面。学习版工程一旦被编译链包住很多新手会卡在“为什么我改了pages/index/index.wxml不生效”。原生工程则没有这层黑匣子。2.2 一份能用的学习版源码至少要摆出这五个文件拿到源码包后不要急着点编译先按下面这颗文件树核对工程结构learning-bot/ ├── app.js ├── app.json ├── sitemap.json ├── pages/ │ └── index/ │ ├── index.wxml │ ├── index.wxss │ ├── index.js │ └── index.json └── utils/ └── bot.js这个结构刻意精简app.json声明页面和窗口样式pages/index/index.*负责聊天界面与事件utils/bot.js放机器人匹配逻辑。学习版源码如果比这个复杂比如多了miniprogram、cloudfunctions双层目录那大概率是云开发模板你要在导入时选对目录层级否则开发者工具会提示“找不到 app.json”。app.json里最常见的坑是pages第一项就是首页而且navigationBarTitleText最好直接叫“智能机器人 学习版”这样预览时标题栏不会出现默认的“微信”。还要注意style: v2会影响部分组件的默认样式如果你手头源码升级过基础库按钮和弹窗的观感会和旧版不一样别误判成代码坏了。2.3 在微信开发者工具里跑通最小闭环导入步骤并不复杂但顺序错了会一路踩坑先打开微信开发者工具选择“导入项目”再定位到learning-bot目录AppID 选“测试号”即可不需要注册企业主体。导入后等编译完成左侧模拟器应该出现一个聊天窗口。我习惯在导入后立刻做一次“冒烟测试”输入“你好”看机器人回不回复点“教教我”看能不能弹出学习框。这两条路通了说明页面生命周期和 storage 读写都没被破坏。如果屏幕空白先看 Console 报错是不是module is not defined——有些源码把bot.js写成了 Node 风格module.exports在小程序里没问题但要确保是通过require引入而不是直接script引入。下面这段是pages/index/index.js里最关键的发送逻辑学习版源码的差异大多集中在这里// pages/index/index.js const bot require(../../utils/bot); Page({ data: { draft: , messages: [ { id: 1, role: bot, content: 你好我是智能机器人学习版。 } ] }, // 监听输入框内容 onInput(e) { this.setData({ draft: e.detail.value }); }, // 点击发送或键盘发送 onSend() { const text this.data.draft.trim(); if (!text) return; this.pushMessage(user, text); // 机器人本次回复结果由 bot.match 决定 const reply bot.match(text); this.pushMessage(bot, reply); this.setData({ draft: }); }, pushMessage(role, content) { const messages this.data.messages.concat({ id: Date.now() Math.random(), role: role, content: content }); this.setData({ messages }); } });这段代码里setData是整个页面的性能放大器也最容易成为瓶颈。每次发送都会把整个messages数组重新 set 一次学习版没问题但如果以后做到 200 条以上聊天记录建议只追加最后一条或者用wx.nextTick延迟渲染。bot.match是纯函数输入文本、输出回复这保证了逻辑可以被单独测试这也是我坚持把机器人算法扔到utils/bot.js的原因。对应页面模板index.wxml只需要一个滚动列表和一个输入栏view classpage scroll-view classmsg-scroll scroll-y scroll-into-viewmsg-{{messages.length - 1}} view wx:for{{messages}} wx:keyid idmsg-{{index}} classmsg-row {{item.role}} view classbubble{{item.content}}/view /view /scroll-view view classfooter input classinput value{{draft}} bindinputonInput bindconfirmonSend confirm-typesend placeholder说点什么... / button sizemini typeprimary bindtaponSend发送/button /view /view这里scroll-into-view的值要跟列表项id对应否则新消息出来画面不会自动滚到底部。很多学习版源码把scroll-into-view写成了固定字符串新消息被顶到可视区外看上去就像机器人没回复。如果你发现聊天记录不滚动先检查这条绑定关系不要怀疑毫秒级渲染问题。2.4 机器人核心逻辑先跑通再谈智能学习版和最简“if-else”机器人最大的区别是把回复规则做成了数据。下面这版bot.js是第一版能跑但不聪明// utils/bot.js 第一版 const DEFAULT_RULES [ { keywords: [你好, 您好, hi], answer: 你好呀我是智能机器人学习版。 }, { keywords: [名字, 你是谁], answer: 你可以叫我小智。 }, { keywords: [再见, 拜拜], answer: 再见期待下次聊天。 } ]; function match(input) { const text String(input || ).trim().toLowerCase(); for (let i 0; i DEFAULT_RULES.length; i) { const rule DEFAULT_RULES[i]; for (let j 0; j rule.keywords.length; j) { if (text.indexOf(rule.keywords[j]) 0) { return rule.answer; } } } return 我还没学会这句点“教教我”给我补课。; } module.exports { match: match };这段代码的优点是直白缺点是“见词就回”。比如用户说“我不喜欢你”其中包含“喜欢”以外的词但规则里只要有“你”或“喜欢”就会误命中。所以第一版只能当测试骨架真正能用的学习版机器人至少要有“打分 阈值 否定词扣分”这部分在下一章展开。3. 实现“会学习的机器人”关键词评分、阈值与自我扩充3.1 给关键词打分权重、阈值和大小写把规则当成一张评分表比逐个if命中要可靠得多。每条规则都有若干关键词和一个权重机器人统计用户输入里命中了多少个词再乘上规则权重得分最高的规则胜出。阈值的作用是过滤低质量命中用户随口说“嗯”“哦”这种单字不应该触发任何长句规则。下面这段是升级版匹配引擎学习版绝大多数源码里用的就是这种结构// utils/bot.js 第二版 const NEGATIVE_WORDS [不, 别, 没, 非, 无]; function getRules() { const learned wx.getStorageSync(learning_bot_rules) || []; return DEFAULT_RULES.concat(learned); } function match(input) { const text String(input || ).trim().toLowerCase(); if (!text) return 你发送的内容是空的。; const rules getRules(); let bestRule null; let bestScore 0; rules.forEach(function (rule) { let score 0; rule.keywords.forEach(function (kw) { const key String(kw).toLowerCase(); // 用 split 统计关键词出现次数而不是只判断是否存在 const count text.split(key).length - 1; if (count 0) { score rule.weight * count; } }); // 否定词扣分防止“我不想你”命中“想你” NEGATIVE_WORDS.forEach(function (neg) { if (text.indexOf(neg) 0) { score - rule.weight * 0.5; } }); if (score bestScore) { bestScore score; bestRule rule; } }); // 阈值设为 1等于至少要完整命中一个关键词 if (bestScore 1) { return bestRule.answer; } return 我还没学会这句话点“教教我”给我补课。; }这里有两个参数最值得调第一个是rule.weight默认设为 1 就好如果你希望某些规则更强势比如“你是谁”比“你”更能代表人设问答把weight调到 1.5第二个是末尾的阈值bestScore 1如果你发现机器人太容易被单字命中就把阈值提高到 2代价是会暂时忽略很多弱命中。text.split(key).length - 1这个方法比indexOf稍贵但能处理“你好你好”这种重复输入分数会翻倍。真机性能测试时如果聊天变得卡顿多半是这里循环太多因为每一句都要遍历所有规则。学习版规则一般不超过 200 条不需要优化超过 200 条后建议把规则按关键词首字母做一层索引。3.2 教机器人说话本地存储与防重复“学习版”最核心的能力不是回答而是能记住你的纠正。实现方式是在未命中时弹出一个输入框把你给的答案和原问题一起存进wx.setStorageSync。下一次再问同样的问题规则表里多了一条机器人就能命中。下面这段是学习逻辑我会在pages/index/index.js里绑定一个“教教我”按钮// pages/index/index.js 新增方法 onTeachTap() { const pendingText this.data.messages .filter(function (item) { return item.role user; }) .pop(); if (!pendingText) return; wx.showModal({ title: 教机器人, editable: true, placeholderText: 输入这句话的正确回复, success: (res) { if (!res.confirm) return; const result saveLearn(pendingText.content, res.content); wx.showToast({ title: result.msg, icon: result.ok ? success : none }); } }); }对应在bot.js里补上saveLearn和查重const STORAGE_KEY learning_bot_rules; function saveLearn(input, answer) { const rules getRules(); const exists rules.some(function (rule) { return rule.keywords.some(function (kw) { return String(kw).toLowerCase() String(input).trim().toLowerCase(); }); }); if (exists) { return { ok: false, msg: 这句话已经学过了去改规则吧 }; } rules.push({ keywords: [String(input).trim()], answer: String(answer || 我记住了但我不知道怎么回答。).trim(), weight: 1 }); try { wx.setStorageSync(STORAGE_KEY, rules); return { ok: true, msg: 学会啦 }; } catch (e) { return { ok: false, msg: 存储失败规则可能超过容量 }; } }学习功能有两个隐藏坑。第一个坑是重复学习同一个问题教十遍规则表越来越胖最后分布到真机存储里后面几条会被悄悄丢弃。第二个坑是wx.showModal的editable参数需要基础库 2.17.1 以上很多旧手机点“教教我”没反应就是因为微信版本太低后面避坑章会单独说。把用户教过的答案存进本地这一步很符合“学习”的名字但它学到的只是一个个孤立问答还谈不上泛化。想让它举一反三下一步加同义词和上下文。3.3 多轮上下文从“查词典”变成“能接话”用户问“你是谁”机器人回答后紧接着问“你几岁”如果没有上下文机器人会当成新问题去匹配大概率回一句“我还没学会”。学习版机器人至少要能处理一种最简单的情况指代词“你”“它”“这个”接上上轮话题。我一般会在bot.js里加一个模块级变量暂存上一句输入// utils/bot.js 顶部 let lastUserInput ; function match(input) { const text String(input || ).trim().toLowerCase(); const pronouns [你, 它, 他, 这个, 那个]; let finalInput text; // 如果这句很短且包含指代词就拼上上一句主语 if (text.length 6 pronouns.some(function (p) { return text.indexOf(p) 0; })) { finalInput lastUserInput text; } // ... 打分逻辑不变用 finalInput 替换 text ... lastUserInput finalInput; return bestScore 1 ? bestRule.answer : 我还没学会这句话。; }这个启发式方案不能处理复杂指代比如“北京天气怎么样”和“那上海呢”它只拼上一句没法做“地点替换”。但学习版要的是低成本、看得懂而不是通用对话引擎。你可以在规则里专门加一条keywords: [上海, 天气]来补足。真想让机器人具备上下文记忆就得把lastUserInput换成wx.setStorageSync让每次打开小程序都记得上一场聊天但这会带来隐私问题学习版不建议默认开启。多轮状态的本质是“用规则外的记忆补全输入”。你在设计时一定要给上下文模块单独开关否则调试时会很痛苦你输入“你好”再输入“你呢”机器人莫名其妙回“你好你呢”而规则里根本没有这句话你根本猜不到是上下文拼出来的。4. 避坑清单智能机器人小程序从模拟器到真机最容易翻车的五个地方4.1 开发工具接口访问正常真机接口访问失败学习版源码如果只在本地做关键词匹配不会有这个问题。但只要有“远程知识库”“接入大模型 API”它就会成为最气人的一个坑微信开发者工具里一切正常一扫码真机预览请求全部失败Console 红字一片。原因是开发者工具默认勾选了“不校验合法域名”而真机环境强制校验request合法域名。解决分两步开发阶段可以在“详情 - 本地设置”里勾选“不校验合法域名”但这不是上线办法正式发布前必须把机器人服务端的域名加到小程序后台的“request 合法域名”里并且要求 HTTPS。如果你用云开发则要确保wx.cloud.init里的环境 ID 对应真实环境而不是开发工具里的默认模拟环境。另外很多学习版源码把机器人回复做成wx.request同步等待。注意小程序的wx.request走的是异步回调不要在onSend里直接return请求结果。你需要把回复渲染放到success回调里或者用async/awaitpromisify。这个错误在模拟器上偶尔能蒙混过关真机上会因为网络延迟显示空白回复。4.2 wx.showModal 的 editable 参数在旧基础库上不弹输入框学习版机器人最常见的教学入口是“弹窗里输入答案”但真机上可能一点反应都没有甚至整个页面卡住。原因不是代码写错而是wx.showModal的editable配置要求基础库版本不低于 2.17.1。你可以在开发者工具里看模拟器版本它通常是最新基础库所以模拟器不报错真机上的微信版本可能很旧尤其是部分安卓设备。排查方式是先获取版本号const info wx.getSystemInfoSync(); wx.showModal({ title: 版本诊断, content: 基础库版本 info.SDKVersion, showCancel: false });如果确认版本太低解决办法是抛弃editable改用页面里的自定义弹层用一个view模拟遮罩里面放input和“保存”按钮逻辑不变但兼容性最好。许多生产级聊天机器人源码都不依赖wx.showModal编辑而是把“教教我”做成一条独立消息流让用户像聊天一样把答案发给机器人这种交互既自然又避开了基础库版本问题。4.3 storage 静默丢规则学习版的记忆说没就没用户教了几十条话术过几天打开小程序机器人又变“失忆”了。这不是诡异 bug而是wx.setStorageSync的两个限制在同时生效单个 key 的值最大约 1MB所有 key 总和最大约 10MB同时在setStorageSync写异常时失败并不一定会冒到 UI 层而是悄悄失败。我的处理习惯是给学习规则增加一条“健康检查”每次保存前把rules序列化成 JSON判断JSON.stringify(rules).length是否超过 800KB如果超了就提示用户导出规则而不是继续追加。还可以在saveLearn返回结果时用getStorageInfoSync确认当前剩余空间发现问题立刻提示。另一个丢数据原因是规则重复导致覆盖。有些学习版源码保存时直接给keywords数组push没有查重于是同一句“你好”被教了十几次后来覆盖了前面的正确答案。上一章saveLearn里的查重逻辑就是为了避免这个问题你拿到任何学习版源码第一件事就是检查它有没有exists判断。没有的话优先级最高的坑就是它。4.4 关键词匹配答非所问否定词和同义词没处理“你喜欢我吗”回应“喜欢你”“我不喜欢你”也回应“喜欢你”这是关键词机器人最尴尬的地方。用户在真机上试一次就会觉得产品是弱智这也是学习版源码最容易劝退人的坑。原因有两个只做正向命中没有否定词扣分同义词太少用户说“你名字叫什么”规则里只有“名字”和“你是谁”就匹配不上。解决方式是给规则加同义词数组并维护一个否定词表这个方案就是上一章的scoreRule逻辑。我给学习版定的处理策略是否定词扣分仅作为兜底不解决所有语义。比如“我不讨厌你”包含“不”和“讨厌”但实际上是正面表达。学习版不需要做到这一步只需要让用户能通过“教教我”把正确答案补进去。真想要语言级理解就接语义接口关键词只是“召回层”。4.5 顶部导航栏高度和消息列表被刘海屏遮挡聊天机器人页面是一个高频全屏交互页面尤其需要适配顶部导航栏。很多学习版源码把页面顶部写死padding-top: 64px在 iPhone 刘海屏上胶囊按钮和状态栏位置不一样消息列表第一条会被状态栏或者导航栏盖住用户拖动聊天记录时总有一截看不到。正确做法是动态读取胶囊按钮位置然后把它当成顶部安全距离// 页面 onLoad 里执行 const menu wx.getMenuButtonBoundingClientRect(); const windowInfo wx.getWindowInfo(); this.setData({ navBarHeight: menu.top menu.height 8, statusBarHeight: windowInfo.statusBarHeight });然后在index.wxss里用padding-top: {{navBarHeight}}px或 CSS 变量去撑开内容。不要用固定值因为不同机型的导航栏高度差异很大。这个坑不仅在聊天页面任何自定义顶部导航的智能机器人页面都会遇到。还有一个小坑scroll-view里的scroll-into-view指向的id不能以数字开头。如果你生成的消息 id 是纯时间戳比如1678888888888iOS 上可能滚动失败安卓正常。学习版源码为了省事只在id里放时间戳这就是“模拟器好、真机不滚”的另一个隐藏原因。解决方法是拼上前缀像idmsg-{{item.id}}。5. 把学习版做成自己真在用的产品回归验证、云端迁移与沉淀习惯5.1 用一组回归用例锁住机器人行为学习版机器人最大的风险是“改一个规则炸了三条对话”。我给每个接手学习版源码的人反复强调必须先在开发者工具里跑一遍回归用例。不需要引入测试框架直接在 Console 里执行// 手动回归脚本贴到开发者工具 Console const bot require(./utils/bot); const cases [ { input: 你好, expected: 你好呀 }, { input: 你叫什么, expected: 小智 }, { input: 再见, expected: 再见 } ]; let pass 0; cases.forEach(function (item) { const reply bot.match(item.input); const ok reply.indexOf(item.expected) 0; console.log(ok ? PASS : FAIL, item.input, , reply); if (ok) pass; }); console.log(通过率, pass / cases.length);这段脚本每天改规则后跑一次能挡住 80% 的回归问题。关键点是expected用indexOf判断片段命中而不是全等因为回复中间可能带标点或前后缀。5.2 从本地规则到云开发数据库容量和团队协作的下一步当规则量超过 300 条或者你要让第二个管理员也参与维护规则本地 storage 就该退休了。此时把getRules改造成从云开发数据库读取是学习版向生产版过渡最稳的一步// 云开发版规则读取示意 const db wx.cloud.database(); db.collection(bot_rules).limit(20).get().then((res) { this.setData({ rules: res.data }); });注意云开发数据库的读权限要设置成“仅创建者可读写”或“所有用户可读”否则用户端读取会失败。这一步迁移后机器人学习能力从“教单机”变成“教云端”但也会引入费用、权限、内容审核等问题学习版阶段不建议一上来就上云先让本地规则跑三个月整理出真实高频问题再迁。5.3 我的收尾习惯把“教教我”的内容同步回规则表我经手过很多学习版源码最后能长期用下去的都有一个共同习惯定期导出学习数据。在utils/bot.js里加一个exportRules函数把 storage 里的规则输出成 JSON再人工合并进DEFAULT_RULES等于把真机上学到的话术反哺到工程里。这样重启、清缓存、换手机都不会丢。这个习惯的本质是“把黑匣子里的数据变回源码”。学习版机器人不是一个黑匣子它每一条规则都应该是你养出来的数据资产。我每次改完规则都不忘在 Console 里跑一遍回归脚本再顺手把新问答补到测试用例里。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进