ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序AI实战:云函数调用图像识别与OCR的完整demo解析

微信小程序AI实战:云函数调用图像识别与OCR的完整demo解析 简介这是一份面向微信小程序开发与人工智能应用入门者的实战Demo围绕人工智能实战场景展示小程序端常见的页面交互与功能接入方式适合课程设计、毕业设计或新手自学参考。压缩包共59个文件以PNG图标、JS逻辑、WXSS样式、WXML页面结构、JSON配置为主辅以GIF动图、JPG图片和Markdown说明整体体积仅1.36MB可直接导入微信开发者工具查看运行效果。资源内部包含关于、首页、个人中心、待办等多个业务页面模块以及自定义消息组件和utils工具库中的功能性脚本覆盖页面搭建、数据绑定、组件复用、工具方法封装等典型环节大量图标、加载动图与人工智能演示GIF便于对照理解交互反馈和视觉表现。借助README说明与演示素材读者可以快速理清AI功能在小程序中的基础实现思路并据此进行二次开发或功能扩展。目前已有242人学习下载适合希望从零搭建AI类微信小程序的学习者。1. 人工智能实战微信小程序 demo一个能跑的端云AI样板说实话我拆过不少自称“人工智能实战”的压缩包大多数解压完要么缺模型文件要么文档和代码对不上。这份《人工智能实战微信小程序demo.zip》不一样的地方在于它不是让你在本机跑一个 Python 脚本自嗨而是一个能直接在微信开发者工具里跑起来的小程序工程。前端页面、云函数、AI 能力调用都齐了解决的是最常见的落地问题——怎么在微信小程序里安全地调用图像识别、OCR 和文本对话这类 AI 接口又不在前端暴露密钥。适合三类人要做毕设或课程设计的学生、刚接手小程序 AI 需求的开发者、想快速验证 AI 产品形态的产品经理。这篇文章按拆包顺序讲重点放在参数设置和真机联调上。2. 工程结构拆解页面、云函数与AI能力的分层方式2.1 demo目录速览哪部分是页面哪部分是云函数解压 zip 之后第一件事是先看目录结构别急着在开发者工具上点“导入”。我一般会先打开 README再对照目录看一遍确认代码的物理边界在哪里。很多报错其实从目录结构就能预判出来页面文件放错位置、云函数目录没右键部署、project.config.json 里的 cloudfunctionRoot 指错路径都是常见操作失误。这份 demo 的典型目录结构是这样的ai-demo/ ├── miniprogram/ │ ├── pages/ │ │ ├── index/ # 首页图像识别入口 │ │ ├── ocr/ # OCR识别页 │ │ └── chat/ # 文本对话页 │ ├── utils/ │ │ └── request.js # 统一的云函数请求封装 │ ├── app.js # 小程序入口初始化云环境 │ └── app.json ├── cloudfunctions/ │ ├── aiImage/ # 图像识别云函数 │ ├── aiOcr/ # OCR云函数 │ └── aiChat/ # 文本对话云函数 ├── project.config.json └── README.md页面代码只负责三件事采集输入、渲染结果、维护页面状态。云函数负责两件事鉴权和转发 AI 接口请求。目录里没有模型文件真正跑模型的是你在云函数里配置的 AI 平台接口demo 充当的是前端与 AI 平台之间的翻译官和守门员。前端不直接感知 AI 平台用的是哪家、哪个版本只需要拿到一个统一结构的返回对象这层隔离是这份工程最值得保留的设计。看这个结构你要有个判断如果只有一个页面、一个云函数那是演示级如果页面和云函数按能力拆分说明作者预留了扩展位。这份 demo 是后者——页面三个、云函数三个一一对应。后续要加新 AI 能力复制一个云函数目录改 handler 即可不用在页面上重写一套状态管理。2.2 为什么把AI调用放在云函数而不是前端直接请求很多第一次接触小程序 AI 开发的人会问为什么不能直接在页面的 onLoad 里 fetch 一下 AI 接口从技术上讲能通但有两个硬伤。第一是密钥安全。AI 平台的 SecretKey 一旦写在小程序前端代码里等于把钥匙挂在门口。小程序代码包在客户端可以被解包字符串明文写在 JS 里抓包或者反编译都能看到。云函数跑在微信云端密钥放在云函数的环境变量里前端永远接触不到这是最直接的隔离。第二是跨域与域名白名单。AI 平台的接口域名未必在微信小程序的 request 合法域名列表里真机上直接请求会报 url not in domain list开发者工具因为勾了“不校验合法域名”能跑一上真机就现原形。云函数发出的请求不受小程序域名白名单限制等于绕开了这层门槛。所以这份 demo 的架构选择是对的前端只调用自家云函数云函数再去调用 AI 平台。整条链路是页面 → 云函数 → AI 平台 → 云函数 → 页面密钥和域名风险都被收口到云端。对比一下两种方案的差异你会更清楚这个选择的代价维度前端直接请求 AI 接口云函数中转密钥安全明文暴露易被反编译环境变量保存前端不可见域名白名单受小程序 request 合法域名限制不受限制调试成本需要配代理抓包定位慢云函数日志直接可见费用控制密钥泄露后无法追溯调用方可以在云函数里记录调用者 OpenID2.3 从按钮点击到结果返回一次完整的调用链以图像识别页为例完整调用链值得走读一遍后面所有排查都围绕这条链路展开。用户点击“拍照识别”按钮页面 JS 执行 wx.chooseMedia 拿到临时文件路径然后先上传到云存储再把 fileID 传给云函数。这里容易踩的第一个坑就是wx.chooseMedia 拿到的是本地临时路径不是云开发 fileID云函数里的 downloadFile 是读不到本地路径的必须先走一次 uploadFile。// miniprogram/pages/index/index.js 关键片段 async function onRecognize() { const res await wx.chooseMedia({ count: 1, mediaType: [image], sourceType: [camera, album], sizeType: [compressed] }) const filePath res.tempFiles[0].tempFilePath wx.showLoading({ title: 识别中 }) try { const uploadRes await wx.cloud.uploadFile({ cloudPath: ai-images/${Date.now()}-${Math.random().toString(36).slice(2)}.jpg, filePath }) const result await wx.cloud.callFunction({ name: aiImage, data: { fileID: uploadRes.fileID } }) this.setData({ result: result.result }) } catch (err) { console.error(识别调用失败, err) } finally { wx.hideLoading() } }这里有几个参数要留意。mediaType 限定为 image避免用户混选视频sourceType 同时开放 camera 和 album真机上两项都有sizeType 用 compressed让微信先做一次基础压缩减少后面上传的流量和时间。wx.cloud.uploadFile 的 cloudPath 建议加一个带日期的前缀目录这样云存储控制台里不会一锅粥。wx.cloud.callFunction 的 name 必须是 cloudfunctions 目录下的文件夹名data 是传给云函数的事件对象。云函数侧的对应逻辑是接收 fileID → 用 cloud.downloadFile 把云端文件下载到函数临时目录 → 读取成 base64 → 调 AI 接口 → 回传结果。// cloudfunctions/aiImage/index.js 关键片段 const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main async (event) { const { fileID } event const res await cloud.downloadFile({ fileID }) const buffer res.fileContent const base64 buffer.toString(base64) const apiRes await callAIImageApi(base64) return { code: 0, data: apiRes } }cloud.DYNAMIC_CURRENT_ENV 是云开发推荐写法它让云函数始终运行在触发它的那个环境里避免代码写死环境 ID。downloadFile 返回的 fileContent 是 Buffer 对象直接 toString(base64) 即可。这里建议加一个 try/catch把 AI 平台的错误码原样透传给前端方便区分是图片问题还是接口问题。回传结构里 code 固定为 0 表示成功非 0 时前端页面统一弹 toast这个约定在三个云函数里保持一致是 demo 里最值得沿用的风格。3. 核心能力实测图像识别、OCR与文本对话的参数设置与返回处理3.1 图像识别DetectType参数与返回结构解析图像识别云函数里调用的通用识别接口通常要传三个核心参数图片 base64、识别类型 DetectType、可选的方向矫正参数。不同 AI 平台的字段名略有差异但语义一致。以通用图像识别接口为例请求体长这样// cloudfunctions/aiImage/index.js 中构造请求体的片段 const payload { ImageBase64: base64, DetectType: [ Porn, // 鉴黄 Terrorism, // 暴恐 Politics // 政治敏感 ], EnableDetectFace: true, EnableDetectQuality: true } const apiRes await axios.post(https://api.example-ai.com/image/recognize, payload, { headers: { Authorization: Bearer ${process.env.AI_KEY} } })DetectType 是一组字符串数组语义是“一类场景返回一组结果”。demo 默认开了三个子类型实际业务里不一定要全开比如社区类小程序只开 Porn 和 Terrorism 就够多开一个就多一次算子调用延迟和费用都会上去。EnableDetectFace 用于返回人脸框坐标EnableDetectQuality 用于判断图片质量比如是否模糊、是否有遮挡。密钥从 process.env.AI_KEY 读取部署云函数的时候在控制台的环境变量里配置不要写在代码里。返回结构通常是嵌套的新手经常被这一层搞晕{ Code: 0, Data: { TaskStatus: Success, Labels: [ { Name: Porn, Score: 0.01, Suggestion: Pass }, { Name: Terrorism, Score: 0.02, Suggestion: Pass } ], FaceRect: [{ X: 10, Y: 20, W: 30, H: 40 }] } }前端渲染时不要直接把整个对象展示出来要按 Label.Name 逐个判断。demo 里的处理逻辑是Suggestion 等于 Pass 直接放行Review 标记人工复审Block 直接拦截并提示用户。这个三段式判断是可运营的不是简单显示“通过/不通过”。Score 是置信度0 到 1 之间越接近 1 越确定。FaceRect 只有在你开了 EnableDetectFace 才有值没开就为空数组前端要做空值判断。这里有一个参数坑值得提前说图片 base64 体积过大接口会报 400 或超时。通用图像识别接口对 base64 大小通常限制在 2MB 以内最稳妥的做法是前端在 chooseMedia 之后用 wx.compressImage 再做一次压缩把最长边压到 1024 像素再上传。这个坑在 4.3 节会展开讲。3.2 OCR识别语言参数、角度矫正与表格位置参数OCR 页的核心参数和图像识别不太一样它不关心图片内容是否违规只关心文字能不能被准确提出来。demo 的 aiOcr 云函数里请求体一般是// cloudfunctions/aiOcr/index.js 中构造请求体的片段 const payload { ImageBase64: base64, Language: zh, // 识别语言 EnableDetectTextAngle: true, // 是否检测文字方向并矫正 EnableDetectTable: false, // 是否检测表格结构 EnablePdf: false, // 是否传入PDF而非图片 PdfPageNumber: 0 }Language 参数是 OCR 最容易翻车的地方。中英混排用 zh 通常没问题但如果你要识别的是纯英文单据务必改成 en 或按平台支持列表里更宽的语言类型。EnableDetectTextAngle 打开后接口会先把旋转过的图片矫正再识别对手机拍歪的收据、名片非常有用代价是响应时间增加一两百毫秒所以不是所有场景都值得开。EnableDetectTable 只在你需要输出表格结构时打开默认关闭因为开启后返回的是表格线坐标甚至 HTML 片段前端解析逻辑完全不同。OCR 的返回结构是数组形式{ Code: 0, Data: { AngleCorrected: true, TextDetections: [ { DetectedText: 订单号, Confidence: 99, Polygon: [...] }, { DetectedText: ABC12345, Confidence: 98, Polygon: [...] } ] } }前端把 TextDetections 遍历用 DetectedText 字段拼成多行文本展示即可。demo 里提供了一个“复制识别结果”按钮实现是把这些文本用换行符 join 起来写入 wx.setClipboardData。这里要注意Confidence 低于 80 的条目建议在界面上标黄提示用户人工核对而不是直接当作可信结果。这个阈值没有标准答案要按你的业务容忍度调。Polygon 是文字区域的多边形坐标数组如果你要做“点击文字定位原图位置”这类交互才用得上普通展示场景忽略它。这里有个常见误用很多人把 Polygon 数据直接 JSON.stringify 塞进 setData页面渲染时卡顿明显。正确做法是只提取 DetectedText 和 Confidence 进页面数据Polygon 留在云函数侧做后处理。3.3 文本对话会话历史、temperature与请求时机控制文本对话页是三个能力里逻辑最重的因为它有上下文。demo 采用非流式方案用户每轮发言页面维护一个消息数组连同历史一起发给 aiChat 云函数。请求体大致长这样// cloudfunctions/aiChat/index.js 中构造请求体的片段 const config { model: process.env.MODEL_NAME || qwen-turbo, temperature: 0.7, maxTokens: 800 } const payload { Model: config.model, Messages: [ { role: system, content: 你是一个耐心的AI助手回答尽量简洁。 }, { role: user, content: 用一句话解释什么是微信云开发 }, { role: assistant, content: 微信云开发是腾讯提供的云端一体化开发平台。 }, { role: user, content: 再讲一下它的数据库 } ], Temperature: config.temperature, MaxTokens: config.maxTokens }Messages 数组是完整的会话历史role 只能是 system、user、assistant 三种。Temperature 控制随机性0.3 以下适合代码生成和事实问答0.7 以上适合文案创作demo 取 0.7 是兼顾对话自然度和稳定性。MaxTokens 控制单次回复的最大长度按产品需求折算一般中文 1 个汉字约等于 1 到 2 个 token800 大概对应四百字左右的回复够日常对话用。一个常见的翻车点是会话历史无限累积。用户连聊 50 轮Messages 数组越来越大请求体超过大模型平台的单次上限接口直接报 400。demo 的处理是在前端截断只保留最近 10 轮再早的内容丢弃。这是最朴素也最实用的记忆窗口方案。云函数侧不做截断因为云函数是无状态的每次调用都是新环境历史维护只能由页面层负责。返回处理上非流式接口一次返回完整文本{ code: 0, data: { choices: [ { message: { role: assistant, content: 云开发数据库是一个JSON文档型数据库… } } ], usage: { promptTokens: 320, completionTokens: 45 } } }choices 数组里的 message.content 就是要展示给用户的回复。usage 字段很有价值demo 把它透传回前端在页脚显示“本次约消耗多少 token”。这是成本可视化的第一步。我一般会把这个值再存一份到云数据库月底统计时直接聚合不用翻平台账单。4. 联调避坑记录真机验证、域名白名单与本地开发三板斧4.1 现象开发者工具里正常真机上云函数超时或返回“env not found”开发者工具里跑得好好的扫码预览到手机上就超时这是 demo 最常见的翻车场景。原因八成是环境 ID 不一致开发者工具默认使用当前打开项目的环境真机预览时小程序从云端加载如果 app.js 里没有显式初始化云环境开发版和体验版会各自绑定不同环境云函数定位不到对应的数据库和存储。解决办法是在 app.js 里显式指定环境 ID// app.js wx.cloud.init({ env: your-env-id, // 云开发控制台的“环境ID”不是环境名称 traceUser: true })有两个细节值得强调。第一env 字段填的是环境 ID形如 prod-3gabc123不是你在控制台自定义的中文环境名填错会直接导致所有云函数调用失败。第二如果项目要区分开发环境和生产环境建议用两个环境 ID通过编译条件切换而不是改一行代码再重新上传。早期我拿到这种 zip 就直接改业务逻辑结果环境 ID 不对排查了两天才发现是初始化的坑。4.2 现象真机请求直接报 request:fail url not in domain list这个报错在开发者工具里通常被“不校验合法域名”选项掩盖一上真机就暴露。原因是小程序前端发起的网络请求必须走 HTTPS且域名要配在小程序后台的 request 合法域名列表里。demo 如果在前端直接调了 AI 平台接口这个错几乎是必现的。解决方式是三层检查。第一确认所有 AI 调用都收口到云函数前端只剩 wx.cloud.callFunction不存在直连外部域名的情况。第二如果业务里必须前端直连某个自有服务去小程序后台配置 request 合法域名注意填的是域名不是 IP。第三开发阶段可以在开发者工具详情面板勾选“不校验合法域名”但发布前必须撤销勾选再用真机完整测一遍。提示如果遇到真机请求报错先按 4.1 到 4.4 的顺序排查环境 ID、域名白名单、图片体积、置信度阈值这四个变量能覆盖绝大多数链接类问题。4.3 现象大图识别时云函数返回 413 或 image size exceeds limit拍一张 4800 万像素的照片直接拿去识别大概率会被 AI 平台拒绝。原因是接口对图片体积和 base64 字符串长度有硬限制常见阈值是 2MB 或 4MB。云函数里 Buffer.toString(base64) 会让数据膨胀约 1.37 倍一张 3MB 的图转完就是 4MB 多超限是必然的。解决分两步。第一步前端压缩在 chooseMedia 之后、上传之前加一道 wx.compressImagewx.compressImage({ src: filePath, quality: 80, success: (res) { // 用 res.tempFilePath 替代原始 filePath 继续上传流程 uploadAndRecognize(res.tempFilePath) } })quality 取 80 是性价比比较高的档位肉眼几乎看不出差异体积能压掉一半以上。第二步如果压缩完还是超限在云函数里加一道防线读 Buffer 后先判断长度超过阈值直接返回友好错误而不是带着超大请求去打 AI 接口。这样至少保证云函数不白跑、不白付费。4.4 现象接口返回成功但识别结果为空数组或文字乱码云函数返回 code 为 0但 TextDetections 是空的或者识别出来的中文变成乱码这种问题最容易让人觉得是玄学。其实原因通常很具体。空数组多半是图片里根本没有清晰文字比如拍照时手抖、光线不足AI 平台返回空也是正常的。乱码多半是 Language 参数设错了或者图片本身是 PDF 截图字符编码处理路径不对。我的排查顺序是固定的。先拿一张白底黑字的测试图跑通接口排除 API 层面的问题。然后打开 EnableDetectTextAngle看是不是方向矫正导致文字翻转。最后调低 Confidence 过滤阈值看是不是得分低于默认阈值被丢弃了。demo 把阈值写成 80这个值不是固定的业务上如果更看重召回率放到 60 试试代价是误报会变多。5. 进阶玩法把单场景demo改造成多场景AI工具箱三个页面、三个云函数只是起点真正有价值的是把 demo 改造成可扩展的 AI 工具箱。我拿到这种工程后的第一个动作是先给云函数做一次接口签名收敛。具体做法是在 utils/request.js 里加一个统一入口// miniprogram/utils/request.js async function callAI(route, payload) { const res await wx.cloud.callFunction({ name: aiRouter, data: { route, payload } }) if (res.result.code ! 0) { throw new Error(res.result.message) } return res.result.data }之后新增 AI 能力不需要新建页面复刻一套逻辑只需在 aiRouter 云函数里维护一张路由表。新能力就变成注册一个路由和处理函数的事页面侧的 loading、错误码、重试逻辑全部复用。第二个值得做的改造是成本可视化。demo 里 usage 字段已经透传到了前端但只展示还不够。我会在云函数里把每次调用的 token 消耗写入一个独立集合比如 aiUsage定期聚合月底复盘时不用猜“这个月 AI 到底花了多少钱”。// aiRouter 云函数内记账片段 const costRecord { route, userId: cloud.getWXContext().OPENID, promptTokens, completionTokens, time: Date.now() } await db.collection(aiUsage).add({ data: costRecord })如果你接的是大模型对话建议把非流式改成流式。小程序的 WebSocket 通道可以比较优雅地做打字机效果体验比非流式强一个档次代价是前端要处理增量文本的拼接和渲染。这个改动不急先跑通非流式再加流式别一步到位。说一个我自己的习惯每次接手这种 zip我都会强制先做一个半小时冒烟测试——导入工程、确认环境 ID、跑通三个页面、真机预览走一遍再开始改代码。早期我拿到 demo 就直接改业务逻辑结果环境 ID 不对排查了两天才发现是初始化的坑。从那以后这个流程再也不跳步了希望帮到你。本文还有配套的精品资源点击获取
返回列表