简介:这份资源面向微信小程序开发者与电商、物流场景的技术人员,聚焦用户收货地址非结构化、难以统一入库的痛点,借助腾讯云地址解析 API 将自由文本自动识别为省、市、区等标准字段,实现地址格式化。压缩包共 18 个文件,约 16KB,以 6 个 js 脚本、5 个 json 配置、3 个 wxss 样式与 2 个 wxml 页面为主,另有少量系统隐藏文件,整体结构轻量,便于直接导入微信开发者工具运行调试。资源已实现 HMAC-SHA1 签名与 Base64 编码等关键环节,并保留确认按钮事件待补全,读者可据此理解 API 接入、请求构造、JSON 响应解析与异常处理的完整链路。目前已有 8242 人学习下载,适合希望快速掌握地址智能识别与标准化落地的初中级开发者参考。
1. 地址解析这件小事,卡住了多少小程序的转化率
用户在小程序里下单,收货地址那一栏永远是转化漏斗上最松的一颗螺丝。有人把“广东省深圳市南山区科技园南路 8 号”整段粘进去,有人只写“科技园南路8号”,还有人把手机号、姓名、地址揉成一坨丢进同一个输入框。后端拿到这串东西,省市区字段全是空的,运费算不出来,电子面单打不出来,最后只能靠客服人工拆。这个项目干的就是把这段脏活接过来:调用腾讯云地址解析接口,把一段自由文本拆成省、市、区、街道、详细地址、姓名、电话,再回填成标准结构。适合正在做电商、外卖、寄件类小程序,又不想自己维护一套地址词典和正则库的开发者。整套东西是一个可运行的测试工程,压缩包解压后直接能看到调用链路,改个密钥就能跑。
2. 工程结构与调用链路:从一段文本到结构化字段
2.1 目录里都有什么
解压后是一个典型的微信小程序工程骨架,没有花哨的构建工具,用微信开发者工具直接导入根目录即可。核心文件分布大致如下:
| 路径 | 作用 |
|---|---|
app.js/app.json | 小程序入口与全局配置,声明页面路由 |
pages/index/index.js | 主页面逻辑,负责收集输入、触发解析、渲染结果 |
pages/index/index.wxml | 输入框、解析按钮、结果卡片的视图层 |
utils/addressParse.js | 地址解析的封装层,统一处理请求与返回 |
config/tencent.js | 密钥与接口地址配置,实际项目里应替换为服务端下发 |
project.config.json | 开发者工具项目配置,含 appid 占位 |
这个分层很朴素,但边界是清楚的:页面只管交互,utils只管协议,config只管凭据。二次开发时最常动的就是utils/addressParse.js和config/tencent.js两个文件,其余基本不用碰。
2.2 一次解析请求的完整链路
用户在输入框粘贴一段文本,点“解析”,页面把文本交给封装层,封装层拼装请求参数、计算签名、发起 HTTPS 请求,拿到 JSON 后做字段归一化,再回传给页面渲染。整条链路里最容易翻车的是签名环节,腾讯云 API 3.0 的签名要求对请求头、请求体做多轮哈希,顺序错一步就返回鉴权失败。
常见做法是把签名逻辑单独抽一个函数,输入是密钥、时间戳、请求体,输出是Authorization头。下面这段是封装层里签名与请求的核心骨架,语言为 JavaScript,运行在小程序环境:
// utils/addressParse.js const crypto = require('./sha256.min.js'); // 小程序无 Node crypto,需引入纯 JS 实现 // 腾讯云 API 3.0 签名:对 canonicalRequest 做多轮 HMAC-SHA256 function buildAuthorization(secretId, secretKey, payload, timestamp) { const service = 'ocr'; const host = 'ocr.tencentcloudapi.com'; const date = new Date(timestamp * 1000).toISOString().slice(0, 10); // 第一步:拼规范请求串,注意 HTTP 方法、URI、查询串、头部、签名头都要小写并排序 const canonicalHeaders = `content-type:application/json\nhost:${host}\n`; const signedHeaders = 'content-type;host'; const hashedPayload = crypto.sha256(payload); const canonicalRequest = [ 'POST', '/', '', canonicalHeaders, signedHeaders, hashedPayload ].join('\n'); // 第二步:拼待签字符串,算法、日期、服务、终态哈希缺一不可 const credentialScope = `${date}/${service}/tc3_request`; const stringToSign = [ 'TC3-HMAC-SHA256', timestamp, credentialScope, crypto.sha256(canonicalRequest) ].join('\n'); // 第三步:逐级派生签名密钥,日期 -> 服务 -> 终态,顺序不能换 const kDate = crypto.hmacSha256(`TC3${secretKey}`, date); const kService = crypto.hmacSha256(kDate, service); const kSigning = crypto.hmacSha256(kService, 'tc3_request'); const signature = crypto.hmacSha256(kSigning, stringToSign); return `TC3-HMAC-SHA256 Credential=${secretId}/${credentialScope}, ` + `SignedHeaders=${signedHeaders}, Signature=${signature}`; } // 对外暴露的解析函数:输入自由文本,输出标准化地址对象 function parseAddress(rawText) { const payload = JSON.stringify({ ImageBase64: '', Text: rawText }); const timestamp = Math.floor(Date.now() / 1000); const authorization = buildAuthorization( config.secretId, config.secretKey, payload, timestamp ); return new Promise((resolve, reject) => { wx.request({ url: 'https://ocr.tencentcloudapi.com', method: 'POST', header: { 'Content-Type': 'application/json', 'X-TC-Action': 'ParseAddress', // 地址解析的具体 Action 'X-TC-Version': '2018-11-19', // 接口版本,写错会报 Action 不存在 'X-TC-Timestamp': timestamp, 'Authorization': authorization }, data: payload, success: (res) => resolve(normalize(res.data.Response)), fail: reject }); }); }这段代码有三个参数值得盯住。X-TC-Action决定调哪个能力,地址解析对应ParseAddress,写错成别的名字接口会直接告诉你 Action 不存在。X-TC-Version是接口版本号,不同版本字段结构可能不同,别随手改。timestamp必须用秒级时间戳,且与服务端时间偏差不能超过五分钟,手机时间不准就会鉴权失败,这是最常见的“玄学”问题之一。
2.3 返回字段的归一化
接口返回的Response里通常带AddressInfos数组,每个元素含Province、City、District、Street、DetailAddress、Name、Phone等字段。原始返回不一定每个字段都有值,归一化层要做的是补默认值、去空白、把“省/市/区”后缀统一。下面这段是归一化函数:
// 把接口原始返回整理成页面可直接绑定的结构 function normalize(response) { if (!response || !response.AddressInfos || !response.AddressInfos.length) { return { ok: false, reason: '未识别到有效地址' }; } const info = response.AddressInfos[0]; return { ok: true, province: (info.Province || '').trim(), city: (info.City || '').trim(), district: (info.District || '').trim(), street: (info.Street || '').trim(), detail: (info.DetailAddress || '').trim(), name: (info.Name || '').trim(), phone: (info.Phone || '').trim() }; }AddressInfos是数组而不是对象,因为一段文本里可能包含多个地址,比如“寄到 A 地,退回 B 地”。默认取第一个是多数场景的合理选择,但做寄件类小程序时最好把数组整个透传给页面,让用户自己选。归一化里对每个字段都做了trim,别小看这一步,接口返回带首尾空格时,前端做省市区联动匹配会匹配不上,排查起来很费时间。
3. 跑起来:密钥配置、请求域名与真机调试
3.1 密钥该放哪,别放前端
config/tencent.js里通常是这样的结构:
// config/tencent.js —— 仅用于本地联调,切勿提交到公开仓库 module.exports = { secretId: 'AKIDxxxxxxxxxxxxxxxx', secretKey: 'xxxxxxxxxxxxxxxxxxxxxxxx', region: 'ap-guangzhou' };新手最容易犯的错是把secretKey直接写进小程序前端然后上传代码。小程序包是可以被解出来看的,密钥等于公开。正确做法是自己搭一个极薄的转发服务,前端只调自己的域名,由服务端持有密钥并完成签名。这个测试工程为了方便演示把签名放在前端,你在正式项目里一定要挪走。常见做法是服务端用官方 SDK 完成签名,前端只传text字段,返回结构保持一致,前端代码几乎不用改。
3.2 请求域名白名单
微信小程序默认只能请求已配置的合法域名。腾讯云 OCR 的接口域名是ocr.tencentcloudapi.com,你需要在微信公众平台后台的“开发管理 - 服务器域名”里把它加进 request 合法域名。本地开发阶段可以在开发者工具里勾选“不校验合法域名”,但真机预览时这个开关不生效,必须配好域名,否则请求直接失败且报错信息很含糊。
配置完成后,在开发者工具里导入项目根目录,填入自己的 appid(测试可用测试号),编译。页面出现输入框后,粘贴一段带省市区和电话的地址文本,点解析。如果返回鉴权错误,先核对时间戳和密钥;如果返回空结果,换一段更完整的文本再试。
3.3 真机调试要看什么
真机调试时打开 vConsole,重点看三样:请求头里的Authorization是否完整、X-TC-Timestamp与当前时间差多少、返回体的Response.Error字段。腾讯云的错误结构是Response.Error.Code加Response.Error.Message,AuthFailure系列基本都是签名或时间问题,FailedOperation系列多半是文本里确实没有可识别地址。把这些错误码在封装层里映射成中文提示,用户体验会好很多,也省得自己反复查文档。
4. 避坑与排查:五个真实踩过的坑
4.1 鉴权失败但密钥没错
现象是每次请求都返回AuthFailure.SignatureFailure,密钥反复核对无误。原因多半是签名时对请求体的哈希和实际发送的请求体不一致,比如先JSON.stringify算了一次哈希,发送时又序列化了一次,两次结果不同。解决办法是只序列化一次,把同一个字符串既用于哈希也用于发送。我一般会在封装层里把payload变量固定下来,禁止在发送前再动它。
4.2 时间戳偏差导致间歇性失败
现象是本地跑得好好的,换一台手机就时好时坏。原因是部分设备系统时间不准,与服务端偏差超过五分钟,签名直接失效。解决办法是在请求前用服务端时间校准,或者捕获AuthFailure后提示用户检查系统时间。更稳妥的做法是让服务端代签,前端根本不碰时间戳。
4.3 返回字段名大小写踩空
现象是页面上一片空白,但接口明明返回了数据。原因是不同接口版本或不同能力返回的字段名大小写不一致,有人按province取,实际是Province。解决办法是归一化层统一做一次映射,页面只认归一化后的字段,永远不直接读原始返回。这个习惯能挡掉大量“接口改了字段名”的连带故障。
4.4 合法域名没配导致真机全挂
现象是开发者工具正常,真机预览全部请求失败。原因是工具里勾了“不校验合法域名”,真机不认这个开关。解决办法是在后台把接口域名加进 request 白名单,并且注意小程序要求域名必须备案、必须 HTTPS。上线前把工具里的校验开关关掉再完整跑一遍,能提前暴露这类问题。
4.5 多地址文本只取了第一个
现象是用户粘贴“寄件地址 A,收件地址 B”,结果只解析出 A。原因是归一化时直接取了AddressInfos[0]。解决办法是把数组透传给页面,用选择器让用户确认,或者在业务层约定输入框只允许单地址并做前端提示。这个坑在寄件场景里出现频率很高,别等用户投诉才发现。
5. 二次开发:把解析能力接进真实下单流程
5.1 从测试页到业务页的迁移路径
这个工程的价值不在测试页本身,而在utils/addressParse.js这层封装。迁移到真实项目时,我的做法是保留封装层的函数签名不变,把内部实现从“前端直连腾讯云”换成“调自己的服务端接口”。这样页面代码一行不用改,密钥也顺势挪到了服务端。服务端收到text后调官方 SDK,返回结构对齐normalize的输入,前端继续走同一套归一化逻辑。改造前后对比如下:
| 维度 | 测试工程做法 | 生产建议做法 |
|---|---|---|
| 签名位置 | 前端 JS 计算 | 服务端 SDK 计算 |
| 密钥存放 | config/tencent.js | 服务端环境变量 |
| 请求域名 | 腾讯云接口域名 | 自有业务域名 |
| 错误处理 | 控制台打印 | 映射为业务错误码 |
5.2 解析结果如何回填表单
真实下单页通常有省市区三级联动选择器。解析出province/city/district后,不能直接赋值,要先在联动数据里按名称匹配到对应的编码,匹配不上就降级为“让用户手动选”。下面这段是回填时的匹配逻辑:
// 按名称在省市区数据里找编码,找不到返回 null 交给用户手选 function matchRegion(regionList, name) { if (!name) return null; const target = name.replace(/[省市自治区特别行政区]/g, ''); const hit = regionList.find(item => item.name.replace(/[省市自治区特别行政区]/g, '') === target ); return hit ? hit.code : null; }这里去掉后缀再比对,是因为接口返回可能带“省”字,而本地数据可能不带,直接全等匹配会大面积失败。匹配失败不要强行塞一个默认值,那会让用户收到寄错地址的包裹,宁可让用户多点两下。
5.3 验证改造是否成功的三个动作
改完之后别只看页面能不能出结果。第一,用十段真实脏数据跑一遍,包括只有详细地址没有省市的、带多个电话的、中英文混排的,看归一化后字段是否合理。第二,把服务端密钥临时改错,确认前端能给出可读的错误提示而不是白屏。第三,在弱网下连续点解析按钮,确认没有重复请求把结果覆盖乱。这三步走完,基本能挡住上线后的大部分问题。
从那以后我每次接地址解析这类第三方能力,都强制先把密钥挪到服务端、把归一化层单独写、把错误码映射成中文,再谈页面好不好看。这套顺序帮我省下了不少返工。希望帮到你。
本文还有配套的精品资源,点击获取