自2023年9月15日起,对于涉及处理用户个人信息的小程序开发者,微信要求,仅当开发者主动向平台同步用户已阅读并同意了小程序的隐私保护指引等信息处理规则后,方可调用微信提供的隐私接口。
相关公告见:关于小程序隐私保护指引设置的公告 | 微信开放社区
公告里已经介绍了相关流程,这里不再赘述。下面我们将着重谈一下代码实现。
首先,我们要知道的一点是如果用户没有同意过隐私协议,调用某些API(具体参看:小程序用户隐私保护指引内容介绍 | 微信开放文档)是会触发隐私协议弹窗的,这种叫触发式隐私协议。如果用户同意,这个API调用还能继续执行,否则就会报失败。所以我们要做的是在这个时候,在当前页面弹出我们自定义的隐私协议alert,并且接收resolve。然后在用户同意和拒绝的时候调用这个resolve。具体代码如下:
// page.wxml
隐私弹窗内容....
// page.js
Page({
data: {
showPrivacy: false
},
onLoad() {
wx.onNeedPrivacyAuthorization(resolve => {
// 需要用户同意隐私授权时
// 弹出开发者自定义的隐私授权弹窗
this.setData({
showPrivacy: true
})
this.resolvePrivacyAuthorization = resolve
})
wx.getUserProfile({
success: console.log,
fail: console.error
})
},
handleAgreePrivacyAuthorization() {
// 用户点击同意按钮后
this.resolvePrivacyAuthorization({ buttonId: 'agree-btn', event: 'agree' })
// 用户点击同意后,开发者调用 resolve({ buttonId: 'agree-btn', event: 'agree' }) 告知平台用户已经同意,参数传同意按钮的id。为确保用户有同意的操作,基础库在 resolve 被调用后,会去检查对应的同意按钮有没有被点击过。检查通过后,相关隐私接口会继续调用
// 用户点击拒绝后,开发者调用 resolve({ event:'disagree' }) 告知平台用户已经拒绝
}
})
这里同意的按钮必须使用系统提供的这个写法,需要指定一个id和回调方法。拒绝的按钮没有什么要求。这里务必要保存resolve,并且在同意和拒绝中调用它,否则触发隐私协议的API的成功和失败回调就不会走。这里有个问题,可能多个页面都有API会触发,但是onNeedPrivacyAuthorization只能注册一个,前面注册的会被后面的覆盖。所以,如果把注册方法写在load里可能造成,页面回来的时候就不会触发了。因此建议这个注册放在show的时候注册。关于这个API的更多内容可以查看:wx.onNeedPrivacyAuthorization(function listener) | 微信开放文档
隐私协议的弹窗是我们自定义的,里面的文本建议根据官方的例子来写,要获取指引的名字,可以通过wx.getPrivacySetting这个API。点击指引前往查看隐私协议的页面,可以我们自己写,也可以用微信官方提供的页面,只需要调用接口wx.openPrivacyContract就行。注意,阅读隐私协议不是必须的,所以你可以强制要求用户前往阅读,也可以什么都不做。
前面我们讲的触发式隐私协议,相对比较麻烦,要去找到这些敏感接口调用的页面,然后全都处理一下。还有一种是主动式隐私协议。主动式隐私协议,就是你在关键入口,主动弹出这个协议窗口,让用户去同意或者拒绝。这时候就没有resolve了。但是这样做有个问题,如果用户点了同意,那么后续这些API都会调用成功,用户和开发者都很高兴。但是万一用户拒绝,如果你没有注册onNeedPrivacyAuthorization并进行适当处理,那后续调用都会失败。这时候偷懒的做法是,用户拒绝后就退出小程序,但是这种做法体验不佳。我们应该尽量让用户能使用其他功能,毕竟这些涉及的API可能我们并不太关心,譬如说用户只是不能上传下载图片,而这些并非我们小程序的核心功能。
从用户体验上来讲,对于使用相关API比较多的小程序,为了避免遗漏和一些特殊场景,建议在入口主动弹出隐私协议。对于用量较少的小程序,可以采用触发式隐私协议。无论我们采取何种做法,我们都将其封装为一个组件。
UI部分没有什么特别的,我们找个写好的,类似微信风格的实现:
相关页面代码:
隐私保护指引
在使用当前小程序服务之前,请仔细阅读{{privacyContractName}} 。如你同意{{privacyContractName}},请点击“同意”开始使用。
.privacy {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, .5);
z-index: 9999999;
display: flex;
align-items: center;
justify-content: center;
}
.content {
width: 632rpx;
padding: 48rpx;
box-sizing: border-box;
background: #fff;
border-radius: 16rpx;
}
.content .title {
text-align: center;
color: #333;
font-weight: bold;
font-size: 32rpx;
}
.content .des {
font-size: 26rpx;
color: #666;
margin-top: 40rpx;
text-align: justify;
line-height: 1.6;
}
.content .des .link {
color: #07c160;
text-decoration: underline;
}
.btns {
margin-top: 48rpx;
display: flex;
}
.btns .item {
justify-content: space-between;
width: 244rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
box-sizing: border-box;
border: none;
}
.btns .reject {
background: #f4f4f5;
color: #909399;
}
.btns .agree {
background: #07c160;
color: #fff;
}
在组件实现里,我们声明了三个属性,forceShow用于主动式隐私协议,设置true的时候可以强制显示。false的时候,则组件显示由注册回调触发,是触发式隐式协议。forceRead控制用户是否需要强制阅读协议。exitOnRefuse指当用户拒绝的时候,是否需要强制退出小程序。我们在页面show的时候,注册隐私协议触发回调,调用getPrivacySetting获取协议的名称。这里用到了组件的pageLifetimes。后面就是实现打开协议,同意和拒绝的回调。同意和拒绝的回调里,我们要判断是否保存了resolve,有的话是触发式的,需要进行调用,调用完了立刻将其清空。因为我们能区分这两种隐私协议,因此我们在拒绝的时候,显示了不同的错误提示:对于主动式的,我们就说部分功能不可用。对于触发式的,我们就说该功能不可用。
完整的js代码如下:
Component({
options: {
pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
},
/**
* 组件的属性列表
*/
properties: {
forceShow: {
type: Boolean,
value: false
},
forceRead: {
type: Boolean,
value: false
},
exitOnRefuse: {
type: Boolean,
value: false
}
},
/**
* 组件的初始数据
*/
data: {
privacyContractName: '',
showPrivacy: false,
_isRead: false
},
/**
* 组件的生命周期
*/
pageLifetimes: {
show() {
const that = this
wx.onNeedPrivacyAuthorization(resolve => {
that.resolvePrivacyAuthorization = resolve
that.setData({
showPrivacy: true
})
})
wx.getPrivacySetting({
success(res) {
that.setData({
privacyContractName: res.privacyContractName,
showPrivacy: that.data.forceShow
})
}
})
}
},
/**
* 组件的方法列表
*/
methods: {
// 打开隐私协议页面
openPrivacyContract() {
const that = this
wx.openPrivacyContract({
success: () => {
that.setData({
_isRead: true
})
},
fail: () => {
wx.showToast({
title: '指引打开失败',
icon: 'error'
})
}
})
},
// 拒绝隐私协议
refusePrivacy() {
if(this.data.exitOnRefuse){
// 直接退出小程序
wx.exitMiniProgram()
return
}
let tips = ""
if (this.resolvePrivacyAuthorization) {
this.resolvePrivacyAuthorization({
event: 'disagree'
})
this.resolvePrivacyAuthorization = null
tips = '该功能不可用'
} else {
tips = '部分功能不可用'
}
this.setData({
showPrivacy: false
})
wx.showToast({
title: tips,
icon: 'error'
})
},
// 同意隐私协议
handleAgreePrivacyAuthorization() {
const {
_isRead,
forceRead
} = this.data
if (_isRead || !forceRead) {
if (this.resolvePrivacyAuthorization) {
this.resolvePrivacyAuthorization({
buttonId: 'agree-btn',
event: 'agree'
})
this.resolvePrivacyAuthorization = null
}
this.setData({
showPrivacy: false
})
} else {
wx.showToast({
title: '请先阅读指引',
icon: 'error'
})
}
},
},
observers: {
"forceShow": function (forceShow) {
this.setData({
showPrivacy: forceShow
})
}
}
})
整个组件的使用非常简单,只需要在相关页面的json文件里引入。如果涉及的页面较多的话,也可以在小程序全局组件配置里引入:
{
"usingComponents": {
"Privacy": "/components/privacy/privacy"
}
}
以及在所有相关wxml里引入:
这里效果图和部分代码参考了:GitHub - 94xy/miniprogram-privacy: 小程序用户隐私保护授权弹窗组件表示感谢。