《小程序隐私保护指引》解决方案(uniapp版)

1,687 阅读8分钟

需求背景

根据微信官方要求涉及处理用户个人信息的小程序开发者,需通过弹窗等明显方式提示用户阅读隐私政策等收集使用规则。

为规范开发者的用户个人信息处理行为,保障用户合法权益,微信要求开发者主动同步微信当前用户已阅读并同意小程序的隐私政策等收集使用规则,方可调用微信提供的隐私接口。

事前准备

  1. 在文档中查看是否使用了相关隐私接口

开发者需在此板块声明所处理的用户信息,微信会根据小程序版本隐私接口调用情况展示必填项,开发者可自主勾选其他项目。隐私接口与对应的处理的信息关系如下:

处理的信息接口或组件
收集你的昵称、头像open-type="chooseAvatar"、wx.getUserInfo (已回收)、wx.getUserProfile (已回收)、(已回收)
收集你的位置信息wx.authorize({scope:'scope.userLocation'})、wx.authorize({scope: 'scope.userLocationBackground'})、wx.authorize({scope: 'scope.userFuzzyLocation'})、wx.getLocation、wx.startLocationUpdate、wx.startLocationUpdateBackground、wx.getFuzzyLocation
收集你选择的位置信息wx.choosePoi、wx.chooseLocation
收集你的地址wx.chooseAddress
收集你的发票信息wx.chooseInvoiceTitle、wx.chooseInvoice
收集你的微信运动步数wx.authorize({scope: 'scope.werun'})、wx.getWeRunData
收集你的手机号open-type="getPhoneNumber"、open-type="getRealtimePhoneNumber"
收集你的车牌号wx.chooseLicensePlate
收集你选中的照片或视频信息wx.chooseImage、wx.chooseMedia、wx.chooseVideo
收集你选中的文件wx.chooseMessageFile
访问你的麦克风wx.authorize({scope: 'scope.record'})、wx.startRecord、RecorderManager.start、、wx.joinVoIPChat
访问你的摄像头wx.authorize({scope: 'scope.camera'})、wx.createVKSession、、、
访问你的蓝牙wx.authorize({scope: 'scope.bluetooth'})、wx.openBluetoothAdapter、wx.createBLEPeripheralServer
使用你的相册(仅写入)权限wx.authorize({scope: 'scope.writePhotosAlbum'})、wx.saveImageToPhotosAlbum、wx.saveVideoToPhotosAlbum
使用你的通讯录(仅写入)权限wx.authorize({scope: 'scope.addPhoneContact'})、wx.addPhoneContact
使用你的日历(仅写入)权限wx.authorize({scope: 'scope.addPhoneCalendar'})、wx.addPhoneRepeatCalendar、wx.addPhoneCalendar
调用你的加速传感器wx.stopAccelerometer、wx.startAccelerometer、wx.onAccelerometerChange、wx.offAccelerometerChange
调用你的磁场传感器wx.stopCompass、wx.startCompass、wx.onCompassChange、wx.offCompassChange
调用你的方向传感器wx.stopDeviceMotionListening、wx.startDeviceMotionListening、wx.onDeviceMotionChange、wx.offDeviceMotionChange
调用你的陀螺仪传感器wx.stopGyroscope、wx.startGyroscope、wx.onGyroscopeChange、wx.offGyroscopeChange
读取你的剪切板wx.setClipboardData、wx.getClipboardData
  1. 小程序后台 - 设置 - 基本设置 - 服务内容声明 - 用户隐私保护指引添加勾选1中所列的对应隐私描述, 若不添加后期提交审核依旧会强制提示去更新协议,1中所列到的方法,项目中若使用到也需在manifest.json文件中mp-weixin模块permission中填写对应描述,微信隐私协议与permission两处必须填写否则会导致功能无法正常使用,并不是所有权限都需要添加permission,这块自己根据微信文档抉择!
    permission填写的作用在于提交审核时,微信会检测隐私保护指引中是否包含所列项,若无会强制要求其去更新隐私保护指引。

    2.1 我司项目中较多的* wx.setClipboardData复制功能 若不填写对应隐私协议升级后则无法使用,建议已添加隐私协议的项目各自检查下是否存在该功能,能否正常使用!

开发步骤

2023.09.14更新:隐私相关功能启用时间延期至 2023年10月17日。在 2023年10月17日之前,在 app.json 中配置 __usePrivacyCheck__: true 后,会启用隐私相关功能,如果不配置或者配置为 false 则不会启用。在 2023年10月17日之后,不论 app.json 中是否有配置__usePrivacyCheck__,隐私相关功能都会启用。

  1. 开发前检查配置

      1.1 先在小程序 app.json 配置中添加 "__usePrivacyCheck__": true,微信所说的 app.json 中配置 __usePrivacyCheck__: true

      1.2 根据项目中使用到的相关隐私接口 增加permission相关权限描述

      即:项目中manifest.json文件中mp-weixin模块中 __usePrivacyCheck__permission, 如下图示例:

"__usePrivacyCheck__": true,
"permission" : {
    "scope.userLocation" : {
        "desc" : "方便基于您的位置,提供更贴心更高效的服务"
    },
    "scope.camera": {
        "desc": "为了上传图片或者视频提供人脸识别认证服务"
    },
    "scope.writePhotosAlbum": {
        "desc": "保存图片到相册的权限"
    },
    "scope.clipboard": {
        "desc": "复制到剪贴板的权限"
    }
}
2. 小程序用户隐私保护授权弹窗组件
  1.    效果预览

1695194772510.jpg

  1.    开发业务小程序隐私弹框组件

        在components下新建app-privacy-modal文件夹,文件夹下新增app-privacy-modal.vue文件,弹框文案各自可根据各自项目需求自定义。

    <template>
        <van-popup :show="showPrivacy" :close-on-click-overlay="false" :closeable="false" :round="true" :z-index="2222222222">
            <view class="privacy">
                <view class="privacy-title">
                    <text>{{ privacyContractName }}</text>
                </view>
                <view class="privacy-content">
                    亲爱的用户,感谢您信任并使用{{ privacyContractName.replace('隐私保护指引', '') }}!我们非常重视用户的隐私和个人信息保护,
                    您在使用我们的产品或服务时,我们可能收集或使用您个人信息的情形,详见<text @click="openPrivacyContract">{{ privacyContractName }}</text>全文,
                    请您认真阅读并充分理解,如您同意我们的政策内容,请点击同意并继续使用本软件。
                </view>
                <view class="privacy-btns">
                    <view class="btn refuse-btn" @click="refuseEvt">拒绝</view>
                    <button id="agree-btn" class="btn" open-type="agreePrivacyAuthorization" 
                    @agreeprivacyauthorization="agreeEvt">同意并继续</button>
                </view>
            </view>
        </van-popup>
    </template>
      
    <script>
    export default {
        data() {
            return {
                showPrivacy: false,
                privacyContractName: '',
            }
        },
        created() {
            this.onNeedPrivacyAuth()
        },
        methods: {
            onNeedPrivacyAuth() { // 获取是否有需要授权的隐私协议
                const version = wx.getAppBaseInfo().SDKVersion;
                if (this.compareVersion(version, '2.32.3') >= 0) {  // 检验基础库是否大于2.32.3
                    wx.getPrivacySetting({
                        success: (res) => {
                            if (res.errMsg == "getPrivacySetting:ok") {
                                this.privacyContractName = res.privacyContractName,
                                this.showPrivacy = res.needAuthorization
                            }
                        }
                    })
                }
            },
            openPrivacyContract() { // 打开隐私协议页面
                wx.openPrivacyContract({
                    success: () => {
                        console.log("*********隐私协议打开成功")
                    },
                    fail: (e) => {
                        console.log("*********隐私协议打开失败", e)
                        uni.showToast({
                            title: "隐私协议打开失败",
                            icon: "error"
                        })
                    },
                })
            },
            agreeEvt() {
                this.showPrivacy = false
                this.$emit("agree")
            },
            refuseEvt() {
                // 方案一: 一直给提示 停留在当前页  强制用户必须同意
                uni.showToast({
                    title: '必须同意后才可以继续使用当前小程序',
                    icon: 'none'
                })
                // this.showPrivacy = false
                // this.$emit("refuse")
    
                // 方法二: 直接退出小程序
                // wx.exitMiniProgram()
            },
            compareVersion(v1, v2) {  // 比较当前版本号
                v1 = v1.split('.')
                v2 = v2.split('.')
                const len = Math.max(v1.length, v2.length)
    
                while (v1.length < len) {
                    v1.push('0')
                }
                while (v2.length < len) {
                    v2.push('0')
                }
    
                for (let i = 0; i < len; i++) {
                    const num1 = parseInt(v1[i])
                    const num2 = parseInt(v2[i])
    
                    if (num1 > num2) {
                        return 1
                    } else if (num1 < num2) {
                        return -1
                    }
                }
    
                return 0
            }
        }
    }
    </script>
      
    <style lang="less" scoped>
    .flex-display(@display: flex, @flexDirection: column,@alignItems: center, @justifyContent: flex-start, @flexWrap: nowrap) 
     { 
          display: @display; 
          flex-direction: @flexDirection; 
          align-items: @alignItems; 
          justify-content: @justifyContent; 
          flex-wrap: @flexWrap; 
      } 
    .privacy {
        width: 650rpx;
        padding: 40rpx 30rpx;
        box-sizing: border-box;
        background-color: white;
    
        .privacy-title {
            .flex-display(@flexDirection: row, @justifyContent: center);
            font-size: 32rpx;
            font-weight: bold;
        }
    
        .privacy-content {
            padding: 30rpx 0;
            font-size: 28rpx;
            line-height: 48rpx;
            word-break: break-all;
    
            text {
                color: #2666FF;
            }
        }
    
        .privacy-btns {
            .flex-display(@flexDirection: row, @justifyContent: space-around);
            padding: 0 30rpx;
            height: 80rpx;
    
            .btn {
                margin: 0;
                width: 300rpx;
                height: 70rpx;
                text-align: center;
                line-height: 70rpx;
                border-radius: 12rpx;
                background-color: @app-theme;
                color: #fff;
    
                &.refuse-btn {
                    width: 200rpx;
                    color: #666;
                    border: 1rpx solid #ECECF3;
                    background-color: #fff;
                    margin-right: 30rpx;
                }
            }
        }
    }
    </style>
      
    
    1.      使用方法

        可以在所有使用了隐私接口的页面都加上该组件,授权一次之后使用所有隐私接口不再需要授权

    // 业务小程序
    <app-privacy-modal />
    
  1. 小程序隐私指引填写范本

    为了分辨用户,开发者将在获取你的明示同意后,收集你的微信昵称、头像
    为了显示距离或推荐附近相关信息,开发者将在获取你的明示同意后,收集你的位置信息
    开发者收集你的地址,用于获取位置信息
    开发者收集你的发票信息,用于维护消费功能
    为了用户互动,开发者将在获取你的明示同意后,收集你的微信运动步数
    为了通过语音与其他用户交流互动,开发者将在获取你的明示同意后,访问你的麦克风
    开发者收集你选中的照片或视频信息,用于提前上传减少上传时间
    为了上传图片或者视频,开发者将在获取你的明示同意后,访问你的摄像头
    为了登录或者注册,开发者将在获取你的明示同意后,收集你的手机号
    开发者使用你的通讯录(仅写入)权限,用于方便用户联系信息
    开发者收集你的设备信息,用于保障你正常使用网络服务
    开发者收集你的身份证号码,用于实名认证后才能继续使用的相关网络服务
    开发者收集你的订单信息,用于方便获取订单信息
    开发者收集你的发布内容,用于用户互动
    开发者收集你的所关注账号,用于用户互动
    开发者收集你的操作日志,用于运营维护
    为了保存图片或者上传图片,开发者将在获取你的明示同意后,使用你的相册(仅写入)权限
    为了校验身份信息真实性,开发者将在获取你的明示同意后,收集你的车牌号
    开发者访问你的蓝牙,用于设备连接。
    开发者使用你的日历(仅写入)权限,用于用户日历日程提醒。
    开发者收集你的邮箱,用于在必要时和用户联系。
    开发者收集你选中的文件,用于提前上传减少上传时间

方案优势

  1. 对当前项目业务代码无任何代码入侵,没有过多的冗余,所有逻辑均在组件内处理。
  2. 可以在所有使用了隐私接口的页面都加上该组件,授权一次之后使用所有隐私接口不再需要授权。

相关文档及注意事项

  1. 用户隐私保护指引填写说明

  2. 小程序用户隐私保护指引内容介绍

  3. 插件用户隐私保护说明内容介绍

  4. 小程序隐私协议开发指南

  5. 隐私授权弹框的内容均可根据项目自定义,没必要一定要和示例项目一致

  6. 隐私协议弹框若被拒绝项目代码中提供了两种方案,建议使用toast提示引导用户同意授权,尽量避免直接退出小程序操作。