尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序带参二维码获客归因系统设计:scene 参数解析、短码映射与海报合成实战

微信小程序带参二维码获客归因系统设计:scene 参数解析、短码映射与海报合成实战 做私域电商的小程序几乎都绕不开一个需求每个分销员、每场地推活动、每篇公众号推文、每个门店物料都要能追踪这个用户到底从哪个渠道进来的。微信提供的带参小程序码wxacode就是官方归因入口但 scene 参数只有 32 个字符的限制直接用会踩一连串坑。本文记录我们在多租户 SaaS 电商系统中落地获客归因链路的完整方案包含表结构设计、短码映射、海报合成以及 4 个真实踩坑点。一、需求与整体链路归因系统要回答三个问题用户扫了谁的码分销员/门店/渠道这个码对应什么业务场景分销绑定、活动落地页、商品详情、拼团用户后续的注册、下单、复购如何回溯到最初的渠道整体链路如下业务后台生成归因任务 → 短码服务生成 6-8 位短码 → 调微信接口生成小程序码 → Canvas/Pillow 合成营销海报 → 用户扫码 → onLoad 解析 scene → 短码反查 → 写入渠道归因关系带 TTL→ 注册/下单时读取归因 → 佣金/统计结算二、scene 参数的 32 字符限制与短码映射2.1 为什么不能直接把参数塞进 scenewxacode.getUnlimited接口生成的小程序码scene 参数最大长度32 个可见字符且只支持!#$()*,/:;?-._~以及字母数字。如果直接拼distributor_id12345activity_id678scene_typeposter很容易超长而且参数里不能出现中文、空格。我们的方案是所有归因信息落库scene 里只放一个短码。2.2 短码表设计CREATETABLEqrcode_scene(idBIGINTUNSIGNEDNOTNULLAUTO_INCREMENT,scene_codeCHAR(8)NOTNULLCOMMENT短码base628位可容纳3.5万亿组合,tenant_idINTUNSIGNEDNOTNULLCOMMENT商户ID多租户隔离,biz_typeTINYINTNOTNULLCOMMENT业务类型1分销员 2活动 3商品 4拼团 5门店,biz_idINTUNSIGNEDNOTNULLCOMMENT业务主键如分销员ID,pageVARCHAR(128)NOTNULLDEFAULTpages/index/indexCOMMENT落地页路径,expire_atINTUNSIGNEDNOTNULLDEFAULT0COMMENT过期时间戳0为永久,scan_countINTUNSIGNEDNOTNULLDEFAULT0,created_atINTUNSIGNEDNOTNULL,PRIMARYKEY(id),UNIQUEKEYuk_scene_code(scene_code),KEYidx_tenant_biz(tenant_id,biz_type,biz_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT小程序码scene短码映射;短码用 base620-9a-zA-Z生成8 位空间足够且全是合法字符。生成时要注意防碰撞importrandom,string ALPHABETstring.digitsstring.ascii_letters# 62 charsdefgen_scene_code(length8):# 用密码学随机源避免被枚举遍历whileTrue:code.join(random.SystemRandom().choice(ALPHABET)for_inrange(length))# INSERT ... 唯一键冲突则重试最多 5 次try:db.execute(INSERT INTO qrcode_scene(scene_code, tenant_id, biz_type, biz_id, page, created_at) VALUES (%s,%s,%s,%s,%s,%s),(code,tenant_id,biz_type,biz_id,page,now_ts))returncodeexceptDuplicateKeyError:continue踩坑点 1不要用自增 ID 转 62 进制当短码。自增 ID 可预测竞品或黑产遍历 scene 就能爬光你所有分销员的推广码甚至伪造扫码关系。随机短码 唯一键重试才是稳妥做法。2.3 小程序端解析// app.js 或落地页 onLoadonLoad(options){// 扫码进入时 scene 是 encodeURIComponent 编码过的constsceneStroptions.scene?decodeURIComponent(options.scene):;if(sceneStr){wx.request({url:${API_BASE}/qrcode/resolve,data:{scene:sceneStr},success:(res){const{biz_type,biz_id,page,redirect_params}res.data;// 1. 异步上报扫码事件不阻塞跳转this.reportScan(res.data);// 2. 写入本地归因缓存wx.setStorageSync(attr_scene,{biz_type,biz_id,ts:Date.now()});// 3. 按业务类型跳转/绑定this.handleAttribution(biz_type,biz_id,redirect_params);}});}}后端 resolve 接口做三件事短码反查、扫码计数 1、返回业务上下文。反查走uk_scene_code唯一索引单行查询QPS 压力很小。三、归因关系的绑定与 TTL 策略扫码不等于转化。用户今天扫了分销员 A 的码可能三天后才下单。归因关系需要持久化但又不能一次扫码终身绑定否则分销员之间会恶意抢人。CREATETABLEuser_attribution(idBIGINTUNSIGNEDNOTNULLAUTO_INCREMENT,user_idINTUNSIGNEDNOTNULL,tenant_idINTUNSIGNEDNOTNULL,biz_typeTINYINTNOTNULL,biz_idINTUNSIGNEDNOTNULLCOMMENT归因目标如分销员ID,scene_codeCHAR(8)NOTNULL,sourceTINYINTNOTNULLDEFAULT1COMMENT1扫码 2分享卡片 3搜索,expire_atINTUNSIGNEDNOTNULLCOMMENT归因有效期截止时间,created_atINTUNSIGNEDNOTNULL,PRIMARYKEY(id),UNIQUEKEYuk_user_biztype(user_id,biz_type),KEYidx_expire(expire_at))ENGINEInnoDBDEFAULTCHARSETutf8mb4;关键规则唯一键(user_id, biz_type)同一用户在同一业务类型下只保留一条归因新扫码按覆盖规则决定是否更新我们的策略是未产生过订单的归因允许新渠道覆盖已产生订单的锁定 30 天TTL 字段 定时任务分销归因有效期设 30 天可按商户配置每天凌晨扫idx_expire清理过期记录避免无限堆积下单时实时判断订单结算佣金时不直接信任历史归因而是重新查user_attribution并校验expire_at NOW()防止用过期关系结算佣金。踩坑点 2扫码时用户可能还没注册无 user_id。小程序wx.login是静默的但授权手机号/注册是后置的。正确做法是先把归因写本地 Storage 以匿名 openid 落库等用户注册/授权成功时用 openid 把匿名归因转正到 user_id。漏掉这一步未注册扫码用户的归因会全部丢失。四、小程序码生成与海报合成4.1 调微信接口的注意事项getUnlimited返回的是图片二进制流image/jpeg不是 JSONimportrequestsdefget_wxacode(access_token,scene,page,env_versionrelease):urlfhttps://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token{access_token}resprequests.post(url,json{scene:scene,page:page,check_path:False,# 开发版页面未发布时设 False否则报错env_version:env_version,width:430,line_color:{r:31,g:58,b:104},is_hyaline:False},timeout10)content_typeresp.headers.get(Content-Type,)ifimagenotincontent_type:# 出错时返回 JSON如 {errcode:41030,errmsg:invalid page}raiseRuntimeError(fwxacode error:{resp.text})returnresp.content# jpeg bytes踩坑点 3access_token 必须集中管理。多实例服务各自刷新 token 会互相顶掉微信只认最新的导致偶发 40001 错误。正确做法是把 token 放 Redis 集中缓存提前 5 分钟过期用分布式锁保证全局只有一个实例刷新。4.2 海报合成服务端 Pillow 方案分销海报一般是背景图 小程序码 分销员昵称/头像。我们在服务端用 Pillow 合成避免各手机端 Canvas 兼容性问题fromPILimportImage,ImageDraw,ImageFontimportiodefcompose_poster(bg_path,qrcode_bytes,nickname,avatar_bytesNone):posterImage.open(bg_path).convert(RGB)qrImage.open(io.BytesIO(qrcode_bytes)).convert(RGBA)qrqr.resize((280,280),Image.LANCZOS)# 小程序码贴到底部居中位置设计稿固定坐标poster.paste(qr,(235,1080),qr)drawImageDraw.Draw(poster)fontImageFont.truetype(fonts/SourceHanSansCN-Bold.otf,36)# 昵称超长省略namenicknameiflen(nickname)10elsenickname[:9]…draw.text((60,1390),f{name}邀请您进店选购,fontfont,fill(51,51,51))bufio.BytesIO()poster.save(buf,formatJPEG,quality90)returnbuf.getvalue()踩坑点 4中文字体和 emoji。Linux 服务器默认字体不含中文不手动加载.ttf/.otf字体会画成方框昵称里的 emoji 在 Pillow 里基本无法渲染要在写入前用正则过滤掉 emoji 和特殊符号否则海报上出现乱码方块。五、数据统计与佣金结算的口径归因数据最终要服务两件事渠道效果统计和分销佣金。统计口径分离扫码数scan_count、访问 UV、注册数、下单数、GMV 是漏斗的五层必须分层记录不能只存最终订单。我们用一张qrcode_scan_logscene_code、openid、ts、ip 脱敏做扫码明细注册和下单事件再各自关联归因漏斗转化率才能算准佣金只认真实支付佣金结算以已支付且过售后期的订单为准归因关系在订单创建时快照存 scene_code biz_id 到订单表后续归因关系变化不影响历史订单防刷同一 openid 短时间内对同一 scene 的大量扫码只计一次 UV异常高频扫码如单码日扫数千次且无注册进风控队列。六、小结带参小程序码归因系统的核心就三句话scene 里只放短码业务信息全部落库归因关系带 TTL、按事件快照token 集中管理、海报服务端合成。这套方案在我们多租户电商系统里支撑了分销推广、地推物料、活动海报三类场景单表千万级短码、日均百万级扫码下查询和反查都很稳。后续如果要做公众号文章、视频号直播间的跨渠道归因可以在biz_type上继续扩展链路不用动。
返回列表