最近有个业务需求,需要实现小程序-: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),容易超出长度限制,需对参数进行加密或简写(如用数字编码替代字符串)。
最后,更多技术分享、软件工具分享,关注公众号:巨斧空间掌门