乐趣区

打怪升级小程序评论回复和发帖功能实战二

这次分享下“发帖功能”,这个功能其实风险蛮大的,特别是对一些敏感言论的控制,如果没有做好可能导致小程序 被封 ,所以除了必要的人工审核和巡查以外,我们需要一些 微信安全监测API 的帮忙,在 AI 加持下,现在很多大公司对内容和图片的效率大大提高了。

这个 DEMO 仅是一个流程示例,由于涉及到云函数和“真”敏感图,这里就有文字图代替。

发帖的功能只要理清思路,其实并不复杂,利用机器 AI 做 内容审查 是关键,直接关系到小程序的整体安全。

[toc]

用户选择手机图库或拍照

let tempImg = 0; // 代表已选择的图片
wx.chooseImage({
    count: 3 - tempImg.length, // 选择不超过 3 张照片, 去掉当前已经选择的照片
    sizeType: ['original', 'compressed'],  // 获取原图或者压缩图
    sourceType: ['album', 'camera'],  // 获取图片来源 图库、拍照
    success(res) {
        // tempFilePath 可以作为 img 标签的 src 属性显示图片
        let tempFilePaths = res.tempFilePaths;
        console.log(tempFilePaths);
        
        // 举例:这里可以 size 来判断图片是否大于 1MB,方便后面内容检查
        if (res.tempFiles[0] && res.tempFiles[0].size > 1024 * 1024) {console.log("图片大于 1MB 啦")
        }
    }
})

这里用到的方法是 chooseImage,它可以设置让用户选择手机图片库和拍照获得,需要注意的是考虑到后面要用微信自带 API 做图片安全检查,图片大小不能超过 1MB,所以需要设置sizeTypecompressed

内容检查(重点)

由于内容安全对于小程序运营至关重要,稍有不慎就容易导致小程序被封,所以在这块的校验除了常规人工检查外,我们还可以用到微信的内容安全 API。

为什么用微信官方提供的 API?
主要有二点:有一定的免费额度,基于企鹅大厂的专业 AI 检查。

1、云函数 + 云调用

目录结构

├─checkContent
│      config.json   // 云调用的权限配置
│      index.js   // 云服务器 node 入口文件
│      package-lock.json 
│      package.json   // NPM 包依赖
│ ...

为什么要强调这个?
因为本人一开始在用云函数 + 云调用的时候,经常会出现各种不明 BUG,很多都是因为目录里面少传文件,或者少配置。

云函数内容:

const cloud = require('wx-server-sdk');
cloud.init();
exports.main = async (event, context) => {console.log(event.txt);
    const {value, txt} = event;
    try {
        let msgR = false;
        let imageR = false;
        // 检查 文字内容是否违规
        if (txt) {
            msgR = await cloud.openapi.security.msgSecCheck({content: txt})
        }
        // 检查 图片内容是否违规
        if (value) {
            imageR = await cloud.openapi.security.imgSecCheck({
                media: {header: { 'Content-Type': 'application/octet-stream'},
                    contentType: 'image/png',
                    value: Buffer.from(value)
                }
            })
        }
        return {
            msgR,   // 内容检查返回值
            imageR   // 图片检查返回值
        };
    } catch (err) {
        // 错误处理
        // err.errCode !== 0
        return err
    }
}

这里主要用到 security.msgSecChecksecurity.imgSecCheck这 2 个微信开放云调用方法(需开发者工具版本 >= 1.02.1904090),以往我们还要在服务器上单独写个方法,现在变得十分的方便,直接在云函数中调用即可。

这里需要重点说 2 个点

  • 图片 security.imgSecCheck 方法只能接收 buffer,所以需要把 temp 的临时图片转化为 buffer 的形式传过去,我们这里用到 getFileSystemManager 的方法。
  • 如果目录文件中没有config.json,需要自己建一个,并且做一个授权的配置。
{
    "permissions": {
        "openapi": [
            "security.msgSecCheck",
            "security.imgSecCheck"
        ]
    }
}

2、检查文字内容安全

wx.cloud.callFunction({
    name: 'checkContent',
    data: {txt: "乐于分享,一起进步"},
    success(_res) {console.log(_res)
    },
    fail(_res) {console.log(_res)
    }
})

// 返回值参考
{
    "errMsg": "cloud.callFunction:ok",
    "result": {
        "msgR": {
            "errMsg": "openapi.security.msgSecCheck:ok",
            "errCode": 0
        },
        "imageR": false
    },
    "requestID": "77952319-b2b4-11e9-bdc8-525400192d0e"
}

应用场景举例:

  • 用户个人资料违规文字检测;
  • 媒体新闻类用户发表文章,评论内容检测;
  • 游戏类用户编辑上传的素材 (如答题类小游戏用户上传的问题及答案) 检测等。频率限制:单个 appId 调用上限为 4000 次 / 分钟,2,000,000 次 / 天 *

通过 wx.cloud.callFunction 的方法调用 checkContent 的云函数,检查一段文本是否含有违法违规内容。

3、检查图片内容安全

// 获取 temp 临时图片文件的 buffer
wx.getFileSystemManager().readFile({filePath: tempImg[0],  // 这里做示例,所以就选取第一张图片
    success: buffer => {console.log(buffer.data)
        // 这里是 云函数调用方法
        wx.cloud.callFunction({
            name: 'checkContent',
            data: {value: buffer.data},
            success(json) {console.log(json.result.imageR)
                if (json.result.imageR.errCode == 87014) {
                    wx.showToast({
                        title: '图片含有违法违规内容',
                        icon: 'none'
                    });
                    console.log("bad")
                } else {// 图片正常}
            }
        })
    }
})

// 返回值参考
{
    "errMsg": "cloud.callFunction:ok",
    "result": {
        "msgR": false,
        "imageR": {
            "errMsg": "openapi.security.imgSecCheck:ok",
            "errCode": 0
        }
    },
    "requestID": "c126353c2d-b40b-11e9-81c4d-525400235f2a"
}

应用场景举例:

图片智能鉴黄:涉及拍照的工具类应用 (如美拍,识图类应用) 用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等;
敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 2000 次 / 分钟,200,000 次 / 天 *(图片大小限制:1M)

这里先要用 getFileSystemManager() 获取临时图片的 buffer(这个是重点),然后再通过 wx.cloud.callFunction 的方法调用 checkContent的云函数中 security.imgSecCheck 的方法,校验一张图片是否含有违法违规内容。

一开始本人调试的时候,也遇到无法上传的问题,必须通过文件管理 (getFileSystemManager) 获取 buffer 后才能上传检查图片,耗费了本人不少 debugger 时间。

完整代码

原本想做个实际的 demo(代码片段)分享给大家打开参考的,但是云函数必须是一个已注册的 APPID,无奈只能贴代码。

这里主要还是提供一个整体思路,希望能帮助大家减少开发成本,更好的解决问题和完成任务 ^_^

html 部分:

<!-- pages/post /index.wxml -->
<view class="wrap">
        <view class="title">
                <input placeholder="智酷方程式,乐于分享" maxlength="30"  bindinput="getTitle"/>
        </view>
        <view class="content">
                <textarea auto-focus="true" maxlength="200" bindinput="textareaCtrl" placeholder-style="color:#999;" placeholder="关注公众号,一起学习,一起进步" />
                <view class='fontNum'>{{content.length}}/200</view>
        </view>
        <view class="chooseImg">
                <block wx:for="{{tempImg}}" wx:for-item="item" wx:key="ids" wx:for-index="index">
                        <view class="chooseImgBox">
                                <image src="{{item}}" />
                                <view data-index="{{index}}" catch:tap="removeImg" class="removeImg"></view>
                        </view>
                </block>
                <!-- 判断图片 大于等于 3 张的时候 取消 更多 -->
                <block wx:if="{{tempImg.length < 3}}">
                        <view class="chooseImgBoxMore" catch:tap="choosePhoto">
                                <view class="arrow"></view>
                        </view>
                </block>
        </view>
        <view class='submit' catch:tap="submitPost">
                <view class='blue'> 提交 </view>
                <view> 取消 </view>
        </view>
</view>

JS 部分:

Page({
    /**
     * 页面的初始数据
     */
    data: {
        titleDetail: "", // 帖子 title 内容
        content: "", // 发帖内容
        tempImg: [], // 选择图片的缩略图,临时地址},

    /**
     * 生命周期函数 -- 监听页面加载
     */
    onLoad: function (options) {wx.cloud.init();
    },

    /**
     * 生命周期函数 -- 监听页面显示
     */
    onShow: function () {},
    /**
     * 检测输入字数
     * @param {object} e 
     */
    textareaCtrl: function (e) {if (e.detail.value) {
            this.setData({content: e.detail.value})
        } else {
            this.setData({content: ""})
        }
    },
    /**
     * 选择图片
     */
    choosePhoto() {
        let self = this;
        let tempImg = self.data.tempImg;
        if (tempImg.length > 2) {return;}
        wx.chooseImage({
            count: 3 - tempImg.length, // 选择不超过 3 张照片, 去掉当前已经选择的照片
            sizeType: ['original', 'compressed'],
            sourceType: ['album', 'camera'],
            success(res) {console.log(res);
                // tempFilePath 可以作为 img 标签的 src 属性显示图片
                let tempFilePaths = res.tempFilePaths;
                tempImg = tempImg.concat(tempFilePaths);
                console.log(tempImg);
                self.setData({tempImg})

                wx.getFileSystemManager().readFile({filePath: tempImg[0],
                    success: buffer => {console.log(buffer.data)
                        wx.cloud.callFunction({
                            name: 'checkContent',
                            data: {value: buffer.data},
                            success(json) {console.log(JSON.stringify(json))
                                console.log(json.result.imageR)
                                if (json.result.imageR.errCode == 87014) {
                                    wx.showToast({
                                        title: '图片含有违法违规内容',
                                        icon: 'none'
                                    });
                                    console.log("bad")
                                } else {// 图片正常}
                            }
                        })
                    }
                })
            },
            fail: err => {console.log(err)
            }
        })
    },
    /**
     * 删除照片
     */
    removeImg(e) {
        let self = this;
        let index = e.currentTarget.dataset.index;
        console.log(e);
        let tempImg = self.data.tempImg;
        tempImg.splice(index, 1);
        self.setData({tempImg})
    },
    /**
     * 发贴
     */
    submitPost(e) {let { titleDetail, content} = this.data;
        wx.cloud.callFunction({
            name: 'checkContent',
            data: {txt: content},
            success(_res) {console.log(JSON.stringify(_res))

                wx.navigateTo({url: "/pages/postimg/result"})
            },
            fail(_res) {console.log(_res)
            }
        })
    }
})

往期回顾:
[[[打怪升级]小程序评论回复和发贴功能实战(一)](https://segmentfault.com/a/11…
[[填坑手册]小程序 Canvas 生成海报(一)](https://segmentfault.com/a/11…
[[拆弹时刻]小程序 Canvas 生成海报(二)](https://segmentfault.com/a/11…
[[填坑手册]小程序目录结构和 component 组件使用心得](https://segmentfault.com/a/11…

退出移动版