如果你打算或正在开发一款用户可以发布图文内容的小程序,那么内容安全问题一定是一个无法跳过的坎。用户多了人工检查照片的工作量问题暂且不提,首先TX的代码审核这一关都不一定过得去。
审核结果其实关于imgSecCheck
的调用,网上已经有不少的教程了,其思路大致都是使用canvas绘出一张缩略图,然后读取其buffer传至云函数,通过云调用来检验照片并返回结果。
其实本人也使用了同样的流程,并没有开拓出新的思路。虽然如此,但其它教程单图上传的案例居多,并且几乎在都使用旧版的canvas API,所以觉得还是可以把自己的代码拿出来交流一下。
准备
在视图层创建canvas节点
<canvas
id="compress"
type="2d"
style="width:{{cWidth}}px; height:{{cHeight}}px"
>
</canvas>
若不想在屏幕中显示画布,可在style属性中将其设置为绝对定位,并将其位置设置在屏幕范围之外。
比如:
position:absolute; left:-1000px; top:-1000px;
初始化
onLoad: function(options) {
this.imageList = [];
},
// 根据需求添加代码,此处仅为页面设置了imageList属性用以保存审核通过的照片
核心逻辑
添加照片
// 绑定页面中“添加照片”按钮的事件
addImage: function () {
wx.chooseImage({
count: 3 - this.imageList.length, // 可添加照片的最大数量,根据业务需求更改
sizeType: ['compressed'],
}).then(res => {
this.check(res.tempFilePaths); // 为简化案例,添加照片后直接开始审核,根据需求自行修改
})
},
处理照片
为了节约请求时间,调用云函数之前,先将照片进行一次压缩:
- 获取照片原尺寸
- 动态计算压缩后的大小
- 使用canvas进行渲染
- 保存画布到缓存(临时路径)
- 读取缓存中的照片buffer
- 将buffer作为参数传至云函数
由于一些参数会被多次使用,在此,本案例将这些工作封装到了一个函数中,命名为check(array)
用到的API(点击可以查看官方文档)</br>
wx.getImageInfo
wx.createSelectorQuery
wx.canvasToTempFilePath
wx.getFileSystemManager.readFile
check = function (tempFilePaths) {
let checkList = tempFilePaths;
let imageList = this.imageList;
let i = 0; // 用于递归计数
// 定义函数:调用云函数并检验
let cloudCheck = (temp, origin) => {
//... 函数体
};
// 定义函数:照片压缩
let compress = (checkList, i) => {
let path = checkList[i]; //单张照片的临时路径
// 定义函数:使用canvas渲染小图
let render = (path, width, height) => {
//... 函数体
};
// 获取照片比例并计算渲染尺寸
wx.getImageInfo({
//... 参数及成功执行后的处理
});
};
compress(checkList, i);
}
以上代码片段中,我们已经建立好前期处理过程的框架,接下来将各个环节的代码补全。
由于过程中需要用到异步API(不等待API返回结果便执行后面的语句),多张照片不能简单使用循环,需使用递归,即在末尾的
then
方法或回调函数中调用compress()
。
获取照片比例并计算渲染尺寸
由于用户上传的照片长宽比例并不统一,不宜将缩略图的尺寸设为固定。建议先读取到照片信息,动态计算canvas画布大小。
案例中将长边设为256px,通过长宽比确定短边。
以下代码在函数
compress
中。
// 读取照片信息,计算压缩后的大小
wx.getImageInfo({
src: path // 单张照片的临时路径
}).then(res => {
let aspectRatio = res.width / res.height;
let width, height;
// 本案例限制用户上传的照片比例不超过21: 9,根据需求修改或移除限制
if (aspectRatio >= 0.42 && aspectRatio <= 2.35) {
if (aspectRatio >= 1) {
width = 256;
height = Math.floor(width / aspectRatio);
} else {
height = 256;
width = Math.floor(height * aspectRatio);
}
this.setData({
cWidth: width, // 画布宽度(WXML)
cHeight: height // 画布高度(WXML)
});
// 开始绘图
render(path, width, height);
} else {
// 提醒用户照片过长
}
});
开始绘图
以下代码在函数
compress
中;
以下代码在长宽计算结束时被调用,声明应在wx.getImageInfo
之前。
let render = (path, width, height) => {
// 获取视图层的canvas节点,类似前端的 var xxx = document.getElementById("xxxx");
wx.createSelectorQuery()
.select('#compress') // 视图层canvas节点id
.fields({
node: true,
})
.exec(res => { // 回调,res是返回的多个节点组成的数组
let canvas = res[0].node;
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d'); // canvas2d绘图上下文
let img = canvas.createImage(); // 创建img对象(类似HTML的img标签)
img.src = path;
img.onload = () => {
ctx.clearRect(0, 0, width, height); // 渲染前将画布清空(强迫症)
// 新API的drawImage方法接收的第一个参数不再是url,而是img对象
ctx.drawImage(img, 0, 0, width, height);
wx.canvasToTempFilePath({
canvas,
x: 0,
y: 0,
destWidth: width,
destHeight: height,
fileType: 'jpg',
quality: 0.8
})
.then(res => {
cloudCheck(res.tempFilePath, path); // (重绘后的图片, 原图片)
// 若还有传入check的照片未压缩,重新调用compress(递归)
if (++i < checkList.length) {
compress(checkList, i);
};
});
};
});
}
读取buffer,调用云函数
imgSecCheck
API提供HTTP请求和云调用两种方式,均以buffer的形式提交数据,且两种请求方式都无法在小程序端完成,因此我们需要读取缩略图的buffer,然后调用云函数。
let cloudCheck = (temp, origin) => {
wx.getFileSystemManager().readFile({
filePath: temp,
success: buffer => {
wx.cloud.callFunction({
name: 'ContentCheck', //云函数的名称
data: {
img: buffer.data
},
}).then(res => {
console.log(res);
if (res.result.errCode == 87014) {
// 发现敏感照片后所做的处理
// 下面的imageR是云函数中定义的返回值
} else if (res.result.imageR.errCode == 0) {
imageList.push(origin); // 将检查通过的照片添加至imageList,以便其他方法调用
}
})
}
});
};
云函数中的逻辑
关于云函数部署的详细过程,可参考小程序开发的内容安全审核
云函数中的逻辑不需太复杂,直接参考现有的其他案例就可以。不过为保证变量和案例中的命名一致,还是在此附上了云函数的代码:
exports.main = async (event, context) => {
try {
let imageR = false;
// 检查图像内容是否违规
if (event.img) {
imageR = await cloud.openapi.security.imgSecCheck({
media: {
header: {
'Content-Type': 'application/octet-stream'
},
contentType: 'image/jpg',
value: Buffer.from(event.img) // 官方文档这里是个坑
}
});
};
return {
msgR,
imageR
};
} catch (e) {
return e
}
};
网友评论