通过二维码分享实现“推荐人-被推荐人”关联关系

34 阅读8分钟

最近有个业务需求,需要实现小程序-:CP名指南 里用户的关联关系,也就是小程序的推荐人关系:

业务操作步骤:

1、用户刘备,在小程序里打开我的页面,里头有个自己的二维码(小程序的二维码含推荐ID),并可以下载二维码到手机上。

2、刘备将这个二维码通过微信发送给好友关羽。

3、关羽收到消息,长按消息识别二维码,打开小程序-CP名指南。进入小程序首页,触发关羽 与 刘备的 绑定关系(来源关系),关羽的推荐人 是刘备。

这样,可以从后台查询知道 谁是从谁那边分享来进入到小程序的。

具体要几个绑定关系,自己后端接口实现逻辑就行。

我这边就是要个树状结构,其实没那么多节点,只需要2个层级。


接下来:看效果、上代码

话不多说,看效果:(方案的优缺点,在文章后面)

前端代码结构:

首页,就是小程序首页了。

登录页,可以是“我的” 这种页面里,一般都有个登录,我这里用的登录,使用的是 不需要用户授权的方法。

二维码页面,就是展示自己的唯一的二维码。

首页:

二维码页:

点击生成二维码:

页面布局我就不调整了,我临时做的,丑了点,没事,能用就行。大家按自己想放哪里放哪里。

获取的推荐ID如下:

扫码后,进入首页,就能获取推荐人放到二维码里的 推荐人ID了。

上代码(公众号:巨斧空间掌门 )

qrcode.wxss

.container { 
  padding: 0 20rpx;
}

app.json

{
  "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/login/login",
    "pages/qrcode/qrcode"
  ],
  "window": {
    "navigationBarTextStyle": "black",
    "navigationStyle": "custom"
  },
  "style": "v2",
  "renderer": "webview"
}

后端接口代码2个,1个是登录 、1个是获取二维码

WechatUtils.java

`package com.ruoyi.common.utils;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

@Component
public class WechatUtils {

@Value("${wx.applet.app-id}")
private String appId;

@Value("${wx.applet.app-secret}")
private String appSecret;

@Value("${wx.applet.qrcode-path}")
private String qrcodePath;

public String getAppId() {
return appId;
}
public String getAppSecret() {
return appSecret;
}
// 获取微信access_token
public String getAccessToken() {
String url = "api.weixin.qq.com/cgi-bin/tok…" +
"&appid=" + appId + "&secret=" + appSecret;

HttpResponse response = HttpRequest.get(url).execute();
if (!response.isOk()) {
throw new ServiceException("获取access_token失败:" + response.body());
}

JSONObject result = JSONObject.parseObject(response.body());
if (result.containsKey("errcode")) {
throw new ServiceException("获取access_token错误:" + result.getString("errmsg"));
}
return result.getString("access_token");
}

// 生成带推荐人ID的小程序码(返回Base64字符串,便于前端直接显示)
public String generateMiniQrcode(String referrerId) {
try {
// 1. 获取access_token
String accessToken = getAccessToken();

// 2. 调用微信生成小程序码接口
String url = "api.weixin.qq.com/wxa/getwxac…" + accessToken;

// 构建请求参数(scene为推荐人ID,page为小程序页面路径)
JSONObject param = new JSONObject();
param.put("scene", "referrerId=" + referrerId); // 二维码携带的参数
param.put("page", "pages/index/index"); // 扫码打开的小程序页面
param.put("width", 430); // 二维码宽度
param.put("auto_color", false);
param.put("line_color", JSONObject.parseObject("{"r":0,"g":0,"b":0}"));

// 3. 发送请求获取二维码二进制数据
HttpResponse response = HttpRequest.post(url)
.body(param.toString())
.execute();

if (!response.isOk()) {
throw new ServiceException("生成二维码失败:" + response.body());
}

// 4. 将二进制数据转为Base64(前端可直接通过image组件显示)
byte[] qrcodeData = response.bodyBytes();
return "data:image/png;base64," + Base64Utils.encodeToString(qrcodeData);

} catch (Exception e) {
throw new ServiceException("生成二维码异常:" + e.getMessage());
}
}
}`

  
import com.alibaba.fastjson2.JSONObject;  
import com.ruoyi.common.annotation.Anonymous;  
import com.ruoyi.common.core.controller.BaseController;  
import com.ruoyi.common.core.domain.AjaxResult;  
import com.ruoyi.common.utils.WechatUtils;  
import com.ruoyi.common.utils.http.HttpUtils;  
import com.ruoyi.wechat.domain.ReferrerParam;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.validation.annotation.Validated;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.HashMap;  
import java.util.Map;  
  
@Anonymous  
@RestController  
@RequestMapping("/wechat")  
public class QrcodeController extends BaseController {  
  
  
@Autowired  
private WechatUtils wechatUtils;  
  
/**  
* 生成推荐人二维码  
*/  
@PostMapping("/generateQrcode")  
public AjaxResult generateQrcode(@Validated @RequestBody ReferrerParam param) {  
// 调用工具类生成二维码(返回Base64字符串)  
String qrcodeBase64 = wechatUtils.generateMiniQrcode(param.getReferrerId());  
return AjaxResult.success("获取成功",qrcodeBase64);  
}  
  
@PostMapping("/login")  
public AjaxResult login(@RequestParam String code) {  
try {  
// 1. 用 code 调用微信接口换取 openid  
String appId = wechatUtils.getAppId();  
String appSecret = wechatUtils.getAppSecret();  
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId +  
"&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";  
  
// 调用微信接口  
String result = HttpUtils.sendGet(url);  
JSONObject json = JSONObject.parseObject(result);  
  
// 微信返回格式:{openid: "xxx", session_key: "xxx"}(无 errcode 即为成功)  
if (json.containsKey("errcode")) {  
return AjaxResult.error("获取 openid 失败:" + json.getString("errmsg"));  
}  
  
String openid = json.getString("openid"); // 核心:获取 openid  
  
// 2. 根据 openid 创建/查询用户(业务逻辑)  
// User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getOpenid, openid));  
// if (user == null) {  
// user = new User();  
// user.setOpenid(openid);  
// user.setCreateTime(LocalDateTime.now());  
// userService.save(user);  
// }  
logger.info("用户登录成功,用户ID:{}", openid);  
// 3. 返回用户信息(含 userId 和 openid)  
Map<String,String> res = new HashMap<>();  
res.put("userId", "10000");  
res.put("openid", openid);  
return AjaxResult.success(res);  
  
} catch (Exception e) {  
return AjaxResult.error("登录异常:" + e.getMessage());  
}  
}  
}

另外,在微信小程序开发工具上面,配置扫码识别的编译方式, 方便本地开发测试:

详细调试步骤(开发者工具内)

1. 确认二维码的 scene 参数格式

首先明确后端生成二维码时,scene 参数的格式(之前代码中是 referrerId=推荐人ID)。例如:推荐人 ID 为 1001 时,scene 参数是 referrerId=1001

2. 配置 “自定义编译模式”(模拟扫码)

步骤:

  • 打开微信开发者工具,顶部菜单栏点击  “编译” 右侧的下拉箭头 → 选择  “添加编译模式”
  • 在弹出的配置窗口中:
    示意图:
  • 页面路径:pages/index/index
启动参数:scene=referrerId=1001  
模式名称:扫码测试
    • 页面路径:选择扫码后打开的目标页面(通常是首页 pages/index/index)。
    • 启动参数:填写 scene=referrerId=1001(替换 1001 为实际推荐人 ID,与二维码的 scene 一致)。
    • 模式名称:自定义(如 “扫码测试 - 推荐人 1001”)。
    • 点击  “确定”  保存配置。

3. 运行自定义编译模式,验证效果

  • 点击顶部菜单栏的  “编译” 按钮(或选择刚创建的 “扫码测试” 模式),开发者工具会模拟 “扫码打开小程序” 的场景。
  • 此时首页(pages/index/index)的 onLoad(options) 会接收到 options.scene = "referrerId=1001",自动执行解析逻辑:
    • 控制台会打印 解码后的 scene 参数:referrerId=1001 和 识别到推荐人ID:1001
    • 页面会弹出模态框提示:已识别你的推荐人ID:1001,登录后将自动绑定推荐关系

4. 测试 “不同账号扫码” 的效果(模拟新用户)

若需模拟 “新用户扫码”(即未登录状态),只需在调试前 清除本地存储的用户信息

  • 开发者工具左侧菜单栏点击  “Storage” (存储)。
  • 找到并删除 userInfo 和 tempReferrerId 字段(模拟新用户未登录)。
  • 重新运行 “扫码测试” 编译模式,会看到:
    • 弹窗提示推荐人 ID。
    • 若点击 “去登录”,登录后会自动绑定推荐关系(触发 bindReferrer 接口)。

三、真机测试(用另外的账号扫描)

如果需要用真实手机、真实账号测试(如用 A 账号生成二维码,B 账号扫码),步骤如下:

1. 确保本地服务可被真机访问

  • 开发者工具顶部菜单栏点击  “详情”  →  “本地设置”  → 勾选  “不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书” (避免域名校验失败)。
  • 确保手机和电脑在 同一局域网(连接同一个 WiFi)。
  • 开发者工具顶部的  “预览” 按钮 生成二维码,用手机微信扫描,打开小程序(此时小程序可访问本地后端服务 http://localhost:8080)。

2. 用 A 账号生成二维码,B 账号扫码

  • A 账号(推荐人) :在真机上登录 A 微信账号,进入小程序生成推荐人二维码(保存到手机)。
  • B 账号(新用户) :用另一部手机登录 B 微信账号,扫描 A 生成的二维码:
    • 会打开小程序首页,自动解析 scene 参数。

    • 弹窗提示 “已识别推荐人 ID:xxx”。

    • B 账号登录后,自动绑定与 A 的推荐关系(后端可在数据库中查看绑定记录)。

优缺点

优点就不说了,实现简单,说说缺点:

核心缺点

1. 微信合规风险高(最关键痛点)

  • 禁止诱导分享:微信明确禁止 “扫码得奖励”“分享后解锁功能”“分销返利” 等诱导行为,一旦触发,可能导致小程序下架、接口封禁。
  • 限制商业裂变:若用于电商分销、多级返利,容易触碰微信 “三级分销” 红线,合规成本高。
  • 二维码传播限制:微信不允许在朋友圈直接发小程序码(需配图 + 文字引导),且禁止批量群发二维码(易被判定为骚扰)。

2. 传播场景有限,裂变范围受约束

  • 无法跨平台传播:二维码仅能在微信内识别(外部平台如抖音、微博扫码,会跳转微信打开,增加操作步骤)。
  • 朋友圈传播受限:直接发小程序码会被微信压缩或限流,需搭配海报设计(二维码 + 文案 + 产品卖点),增加设计成本。
  • 不适合公域裂变:无法在搜索引擎、短视频平台等公域渠道传播,只能依赖私域流量。

3. 存在作弊风险,需额外防刷机制

  • 自我邀请作弊:用户注册多个微信账号,用自己的推荐码扫码注册,套取推荐奖励(如积分、优惠券)。
  • 批量注册作弊:工作室利用脚本批量生成账号 + 扫码,制造虚假裂变数据,占用业务资源。
  • 解决成本:需后端添加防刷逻辑(如校验设备号、手机号唯一性、IP 地址频次限制),增加开发成本。

4. 数据统计与用户追溯有限

  • 无法追踪二维码传播路径:仅能统计 “谁推荐了谁”,但无法知道二维码被分享到哪个社群、被多少人查看(未扫码),传播效果归因不精准。
  • 被推荐人取消授权后无法追溯:若被推荐人删除小程序、清除存储,后续无法关联推荐关系(需后端持久化存储绑定记录)。

5. 参数长度限制

  • 微信scene参数最长仅 32 个字符,若需携带多个参数(如referrerId=1001&channel=社群&activity=双11),容易超出长度限制,需对参数进行加密或简写(如用数字编码替代字符串)。

最后,更多技术分享、软件工具分享,关注公众号:巨斧空间掌门