思路:首先在小程序端通过wx.login向微信服务器发送请求拿到code,然后把这个code发送到我方服务器,我方服务器拿到前端发来的code之后,携带code, appid,secret,grant_type:'authorization_code这四个参数向微信服务器'https://api.weixin.qq.com/sns/jscode2session,这个接口发送请求拿到openid与session_key,并通过openid进行查询数据库里面这条信息看看有没有手机号,有的话就是老用户,并把这条信息发给前端,如果没有手机号,就说明是新用户,然后后端并把openid发送给前端,还携带一个isnew:true,小程序端按照这个判断获取手机号的按钮是否显示,前端在页面定义一个变量进行存一下这两个参数
接下来是新用户的思路因为是新用户,传过去的isnew:true,所以在小程序界面有两个按钮,一个是用来获取用户信息 wx.getUserProfile,一个是用来获取手机号<button wx:if="{{avatarUrl}}" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">绑定手机号</button>,用来获取用户信息先显示,因为他通过isnew:true判断是否显示,获取用户信息之后,变量存一下,这时候获取手机号是通过用户信息是否存在来控制显示,这时候获取手机号的按钮就会显示,在点击获取手机号按钮的时候,会传一个code,这个code与wx.login不一样,这时候前端调用一个接口,把用户信息与code,openid发给后端,后端接收到这些参数之后,首先通过这个接口获取access_token,需要两个参数还是appid与secret
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}
获取到access_token之后通过它为参数,并携带第二个code进行获取用户手机号,调用下面这个接口
const phoneUrl = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`
const { data } = await axios.post(phoneUrl,{ code })
此时拿到用户手机号.通过这个手机号去数据库查一下数据,要是存在就说明这个手机号已经绑定,要是不存在就把,手机号,openid,用户信息存到数据库里面,存完之后在取一下这条数据返回给前端,前端拿到用户信息,把头像与昵称进行渲染
拉新实现原理:
就是在小程序第二次返回后端的数据加上分享人id,与活动id,然后后端进行入库,通过用户表里面的分享人id,与活动id,进行返现
被灰产薅羊毛的漏斗
被灰产薅羊毛的漏斗就是,把手机号返回给了前端,前端通过调用接口进行入库,这样只要改变手机号,就会创建新用户进行返现,只要把手机号存在后端,在后端进行存库就解决了
后端代码
const Router = require("koa-router");
const user = require('../db/model/userModel')
const {secret} = require("../config/config")
const jsonWebToken = require("jsonwebtoken")
const axios = require('axios')
const router = new Router({prefix:'/user'})
router.post('/wxlogin',async (ctx)=>{
// 从body获取用户发送的参数
let {code} = ctx.request.body //这里的code是小程序前端通过wx.login获取到code,传到后端
// 请求微信的服务器 用code 换取opendid 用户在微信的主键id
let url ='https://api.weixin.qq.com/sns/jscode2session'
// 携带的参数
let params={
appid:'wx5e6344cedcbf76a9', //在微信公众平台上的参数
secret:'6259cadba107f92719c8c3f36d77cade',//在微信公众平台上的参数
js_code:code,//前端返回来的code
grant_type:'authorization_code'//授权类型,此处只需填写 authorization_code,写死就行
}
let result = await axios.get(url,{params:{...params}})
let {openid,session_key} = result.data//通过请求可以拿到openid
// 将wx的openid 和 自己自己的用户系统进行关联 将opendid 和session_key 存入数据库
console.log(openid)
// 根据opendid 获取 用户数据
let existRecord = await user.findOne({openid}) //通过openid去数据库查看是否有这条数据
console.log(existRecord)
const { phone } = existRecord || {} //通过这条数据去看里面是否有phone,有就是老用户,没有就是新用户
if(phone){
// 手机号存在是老用户
let {address,phone,balance,_id,userName, avatarUrl, nickName} = existRecord
let token = jsonWebToken.sign( {address,phone,balance,_id,userName},secret,{expiresIn:"1d"}) //是老用户的话直接把这些字段生成token返回给前端
ctx.body ={code:0,msg:'登录成功',token,uid:_id, avatarUrl, nickName, isNew: false}
//注意这里的isNew前端通过这个参数,来判断获取手机号的两个按钮是否显示,要是老用户是false就不显示
}else{
ctx.body ={code:0,msg:'用户不存在',isNew:true, openid} //这里把openid返回给前端存一下,通过openid为参数进行获取用户手机号
}
})
//下面这个方法,获取手机号的接口是在前台进行接口调用,前台获取到手机号之后,随便修改手机号,调用wxBindPhone存数据库的接口,就会不不停的往数据库插入数据,也就创建多个新用户了,容易让灰产薅羊毛
router.post('/wxGetPhone',async (ctx)=>{
const appid = 'wx5e6344cedcbf76a9';
const secret = '6259cadba107f92719c8c3f36d77cade';
// 从body获取用户发送的参数
let {code} = ctx.request.body //获取一下前端发来的code,这个code与wx.login返回的code不一样,,这里是获取手机号里面的事件自带的
// 获取access_token
const tokenurl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`
const tokenResult = await axios.get(tokenurl)
const {access_token} = tokenResult.data//首先调用微信获取access_token
// 根据token 和 code 解密手机号返回给前端
const phoneUrl = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`
const result = await axios.post(phoneUrl,{ code }) //通过code去获取手机号返回给前端
ctx.body ={code:0,msg:'登录成功',data: result.data.phone_info}
})
router.post("/wxBindPhone",async (ctx) => {
//下面是对薅羊毛的改造,不在让前端进行获取手机号,进行掉接口存数据,这里手机号通过后端获取,然后直接存
const { nickName, avatarUrl , openid, code } = ctx.request.body
//如果是新用户,用户有个按钮首先会调用wx.getUserProfile来获取用户的头像与昵称,openid是在login获取并存在前台的
//下面的跟上面一样
const appid = 'wx5e6344cedcbf76a9';
const secret = '6259cadba107f92719c8c3f36d77cade';
// 获取access_token
const tokenurl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`
const tokenResult = await axios.get(tokenurl)
const {access_token} = tokenResult.data
// 根据token 和 code 解密手机号返回给前端
const phoneUrl = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`
const { data } = await axios.post(phoneUrl,{ code })
console.log(data)//在这里获取到手机号并没有进行发给前端,而是直接在后端进行存数据库,并不需要前端携带手机号等信息进行调用接口,存储数据,这样就不会呗薅羊毛
let phoneNumber = "" //首先给手机号一个初始值
if(data.errcode) { //如果报错的时候进行返回false
ctx.body = data
return false
} else { //这里拿到手机号
phoneNumber = data.phone_info.phoneNumber
}
console.log(ctx.request.body)
let isExist = await user.findOne({phone: phoneNumber})//通过手机号到数据库查询数据
if(isExist){
// 手机号已存在
ctx.body ={ code:-1,msg:'手机号已绑定' }
}else{ //数据库没有这个手机号就说明是新用户,就把前端返回来的数据加上手机号存到数据库里面
let insertR = await user.insertMany({openid,nickName, avatarUrl, phone: phoneNumber})
let userInfo = await user.findOne({openid, phone:phoneNumber }) //存完数据之后在取一下这条数据,并生成token返回前端
let {address,phone,balance,_id,userName} = userInfo
let token = jsonWebToken.sign( {address,phone,balance,_id,userName},secret,{expiresIn:"1d"})
ctx.body ={code:0,msg:'登录成功',userInfo, token}
}
console.log("xxxxx")
})
小程序代码
login.wxml
<view>
<button bindtap="login"> 登录 </button>
<block wx:if="{{isNew}}">
<view class="model">
<button wx:if="{{avatarUrl}}" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">绑定手机号</button>
<button wx:else bindtap="getUserProfile"> 绑定手机号 </button>
</view>
</block>
</view>
login.js
const {wxlogin, getPhone, wxBindPhone} = require("./service")
const storage = getApp().storage;
Page({
openid: null,
phone: null,
data: {
isNew: false,
},
getUserProfile() {
wx.getUserProfile({
desc: 'desc',
success: (res) => {
const {
avatarUrl,
nickName
} = res.userInfo
this.setData({
avatarUrl,
nickName
})
}
})
},
login() {
wx.login({
success: async (res) => {
const { openid, isNew, nickName, token,avatarUrl } = await wxlogin({ code: res.code }, true) || {}
this.openid = openid
if(isNew) {
this.setData({isNew})
} else {
storage.set("token",token)
storage.set("userInfo",{ nickName, avatarUrl})
wx.reLaunch({
url: '/pages/my/my',
})
}
}
})
},
// 获取手机号
async getPhoneNumber(e) {
// const result = await getPhone({ code: e.detail.code })
const payload = {
openid: this.openid,
code: e.detail.code,
nickName: this.data.nickName,
avatarUrl: this.data.avatarUrl,
// 邀请人id
// 活动id
}
const res = await wxBindPhone(payload)
const { token, userInfo } = res || {}
storage.set("token",token)
storage.set("userInfo", userInfo)
this.setData({ isNew: false })
},
})