前言
我们项目开发了一个安卓的app,目前没有上架打算,但会花钱进行推广,产品提了一个需求,需要我们去计算用户通过推广链接进入官网的用户量,到官网下载app安装后注册的转化率,用以计算在哪个渠道推广的效率和性价比更高。
这类需求有一个相对简单的方案,就是在下载前强制要求用户在官网注册,用户注册前记录一个访客,注册时将访客关联用户,下载后记录此用户已下载app,这种就算是个相对有效的转化率计算方案,也是个相对成熟的方案,同时我们产品的官网也支持注册,只需要加个强制注册的逻辑就行了。但产品提出,不希望在官网强制注册后下载,担心会降低下载量,在这个前提下,实现浏览器官网下载app到app内注册的转化
分析需求
- 用户在官网访问时,创建一个访客
- 下载app时调用接口,该访客已下载app
- 用户安装app并打开,读取到浏览器中的访客(?),注册接口将访客Id传上去做关联
技术方案
方案一:使用设备唯一标识
我们第一时间考虑的是,是否可以通过某种设备唯一标识来作为桥梁,如果浏览器和 App 都能读取同一个设备 ID,那么就可以准确地将两者关联。然而,受限于浏览器安全机制,前端 JavaScript 无法获取设备硬件标识符(如 IMEI、Android ID 等),这些信息只能在原生 App 环境中获取。即便通过某些指纹识别技术,也只能依赖浏览器行为生成的 fingerprint,而这种标识在浏览器更换、隐私保护等场景下不可靠。因此该方案不可行。
方案二:利用剪切板传递访客 ID(最终采用)
最终采用了一种可操作性较高、实现成本较低的方案:
- 用户首次访问官网时生成访客 ID,保存在
localStorage中; - 当用户点击下载按钮时,将该访客 ID 复制到剪贴板;
- 用户安装 App 并首次打开时,App 读取剪贴板内容,从中获取访客 ID;
- 在用户注册时,携带该访客 ID 与注册信息一并提交,实现关联。
该方案的优势:
- 不依赖浏览器或系统层的硬件信息
- 实现简单,部署成本低
- 不影响用户下载体验(无需登录/注册)
缺点:
- 如果用户使用多个浏览器访问,可能生成多个访客 ID,导致统计偏差;
- 如果用户在下载后复制了其他内容,可能导致剪贴板中的访客 ID 被覆盖;
- 某些浏览器/系统可能限制剪贴板访问。
在与产品沟通后,确认允许一定程度的误差,因此我们采用了此方案。
代码实现
官网
// useVisitorRecord.js
import { clientPost, clientPut } from '~/api/request'
import isMobile from '~/utils/isMobile'
const userSource = useUserSource()
const visitorRecordKey = 'XX_Visitor_Id'
const visitorIdPrefix = 'XX_VISITOR_ID_'
const getLocalVisitorId = () => {
const visitorId = localStorage.getItem(visitorRecordKey)
if (visitorId) {
return visitorId.replace(visitorIdPrefix, '')
}
return ''
}
const setLocalVisitorId = (visitorId) => {
localStorage.setItem(visitorRecordKey, visitorIdPrefix + visitorId)
}
const createVisitor = async () => {
const res = await clientPost('/visitor', {
clientType: isMobile() ? 2 : 1
})
console.log('createVisitor', res)
if (res && res.code === 200) {
setLocalVisitorId(res.data)
}
}
const updateVisitorInfo = async (params) => {
const visitorId = getLocalVisitorId()
if (visitorId) {
await clientPut(`/visitor/${visitorId}/download-app`, {
clientType: isMobile() ? 2 : 1,
...params
})
}
}
const copyVisitorId = () => {
const visitorId = getLocalVisitorId()
if (visitorId) {
const copyText = visitorIdPrefix + visitorId
if (navigator.clipboard) {
navigator.clipboard.writeText(copyText)
} else {
const copyTextDom = document.createElement('input')
copyTextDom.value = copyText
document.body.appendChild(copyTextDom)
copyTextDom.select() // 选取文本内容;
document.execCommand('Copy')
document.body.removeChild(copyTextDom)
}
}
}
export const useVisitorRecord = () => {
return {
getLocalVisitorId,
copyVisitorId,
createVisitor,
updateVisitorInfo
}
}
// 下载按钮点击事件
const handleBtnClick = () => {
const visitorRecord = useVisitorRecord()
visitorRecord.copyVisitorId()
visitorRecord.updateVisitorInfo()
}
app中读取剪切板,这里使用的是uniapp实现
const visitorRecordKey = 'XX_Visitor_Id'
const visitorIdPrefix = 'XX_VISITOR_ID_'
export const useUserStore = defineStore('user', {
state: () => ({
// 访客id
visitorId: ''
}),
actions: {
// 初始化本地访客id
async initLocalVisitorId() {
const visitorId = uni.getStorageSync(visitorRecordKey)
if (visitorId) {
// 先取本地访客id
this.visitorId = visitorId.replace(visitorIdPrefix, '')
} else {
// 如果本地没有访客id,则从剪贴板中获取
await this.getVisitorIdFromClipboard()
}
if (this.visitorId) {
this.updateVisitorInfo()
}
},
// 从剪贴板中获取访客Id
async getVisitorIdFromClipboard() {
return new Promise(resolve => {
uni.getClipboardData({
success: res => {
if (res.data.startsWith(visitorIdPrefix)) {
this.visitorId = res.data.replace(visitorIdPrefix, '')
uni.setStorageSync(visitorRecordKey, res.data)
}
},
fail: err => {
},
complete: () => {
resolve()
}
})
})
},
// 创建访客
createVisitor() {
const appStore = useAppStore()
userApi
.createVisitor({ clientType: 3 })
.then(res => {
if (res) {
this.visitorId = res
uni.setStorageSync(visitorRecordKey, visitorIdPrefix + this.visitorId)
}
})
},
// 更新访客信息
updateVisitorInfo() {
const appStore = useAppStore()
if (!this.visitorId) {
return
}
userApi.updateVisitor({
visitorId: this.visitorId,
// 访客访问了app
accessApp: true
})
}
}
}
总结
这算是个比较简单的实现方案,而且免费,但限制也确实存在。但我目前没想到过其他比较好的方案,如果有更好的方案欢迎分享,我来学习学习