uni-sec-check内容安全是unicloud封装了微信小程序的免费接口,文本内容安全识别(msgSecCheck)和音视频内容安全识别(mediaCheckAsync),如果我没选择使用uniapp+unicloud开发的话,可以轻松从插件市场引入uni-sec-check公共模块,完成内容安全检测,包含图片和文字检测,下面就针对文本内容和图片进行安全校验,前置知识肯定需要会uniapp和unicloud等知识。
官方文档使用手册
文本内容安全校验
文本内容安全校验比较容易,只需要将编辑的内容发送给处理函数,接口将立即响应,返回处理结果,经过测试,一般会返回三种类型的敏感提示。
- 如果涉及国家领导人将会返回(政治)
- 如果涉及骂人及侮辱词汇返回(辱骂)
- 如果涉及到色情相关词汇返回(色情),当我输入“啪啪啪”返回色情我就认了,输入“预约”也返回色情,审核有点太严了。
// 引入uni-sec-check公共模块
const UniSecCheck = require('uni-sec-check');
// 初始化实例
const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin',
requestId: this.getUniCloudRequestId(), // 云函数内则写 context.requestId 云对象内则写 this.getUniCloudRequestId()
});
const checkRes = await uniSecCheck.textSecCheck({
content: '', // 文本内容,不可超过500KB
openid: '', // 用户的小程序openid
scene: 2, // 场景值
version: 2, // 接口版本号
});
console.log('checkRes: ', checkRes);
上面代码块是官方的示例,一般官方就给常规的演示,逻辑部分需要自己写,下面是我项目中封装的方法,给大家亮出代码来,也作为一个参考吧。
我将文本安全校验和图片安全校验,全部放到一个云对象中,这样方便统一管理,需要校验文字或者图片,只要调用对应的方法即可,云对象起名为“secCheckContent”。
// 引入uni-sec-check公共模块
const UniSecCheck = require('uni-sec-check');
const db = uniCloud.database();
module.exports = {
//文本安全校验方法textSecCheck({文本内容,openid,场景值,接口版本})
async textSecCheck({content,openid="ozBCI62sKO1jZWxxH_nMoZQSYhHo",scene=2,version=2} = {}){
const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin',
requestId: this.getUniCloudRequestId(), // 云函数内则写 context.requestId
});
const checkRes = await uniSecCheck.textSecCheck({
content,
openid,
scene,
version
})
if (checkRes.errCode === 'uni-sec-check-risk-content') {
return {
code: 400,
errMsg: '内容不合规',
result: checkRes.result
}
}else if(checkRes.errCode){
return {
code: 400,
errMsg: checkRes.errMsg,
result: checkRes.result
}
}
return {
errCode: 0,
errMsg: ''
};
}
在客户端调用云对象,引入textSecCheck方法,根据内容返回校验结果。
const secCheckObj = uniCloud.importObject("secCheckContent",{customUI:true});
let sec = await secCheckObj.textSecCheck({content:formData.value.content});
if(sec.errCode != 0){
uni.showModal({
title:sec.errMsg,
content:`输入的内容违规,涉及“${sec.result.label}”,请重新编辑!`,
showCancel:false
})
smtLoading.value = false;
return;
}
以上就是关于文本内容安全的校验,是不是很简单,是不是以为图片校验也很容易,那就大错特错了,图片校验的难度比文本校验的难度高很多。
检测图片校验鉴黄
注意:
为什么说图片校验麻烦,是因为图片接口不能理解将校验结果返回,V2的检测结果是异步返回的,需要提前在微信公众平台「开发」-「开发设置」-「消息推送」开启消息服务,检测结果在 30 分钟内会推送到你的消息接收服务器,以下是全部步骤。
一、开通微信消息推送
1.「开发」-「开发设置」-「消息推送」点击启用
2.消息推送配置
3.生成URL服务器地址,系统将推送的消息返回到这个地址,接收到推送后处理你的逻辑
1) 创建一个名为secCheckMsg的云函数,单击右键上传并运行。
2)打开unicloud的web控制台,找到名为secCheckMsg的云函数,看一下运行后云函数是否跑通,点击右上角云函数详情。
3)设置云函数URL化的path部分,在域名后面添加一下和云函数名一样的路径即可“/secCheckMsg”
4) 复制以下代码到云函数,并重新单击右键“上传部署”
const crypto = require('crypto')
function getSignature (token, timestamp, nonce, msgEncrypt) {
const str = [token, timestamp, nonce, msgEncrypt].sort().join('')
return crypto.createHash('sha1').update(str).digest("hex")
}
function PKCS7Decode(buf) {
let padSize = buf[buf.length - 1]
return buf.slice(0, buf.length - padSize)
}
function decryptMsg (encodingAESKey, msgEncrypt) {
const key = Buffer.from(encodingAESKey + '=', 'base64')
const iv = key.slice(0, 16)
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
decipher.setAutoPadding(false)
let deciphered = Buffer.concat([decipher.update(msgEncrypt, 'base64'), decipher.final()])
deciphered = PKCS7Decode(deciphered)
const content = deciphered.slice(16)
const length = content.slice(0, 4).readUInt32BE(0)
return {
message: JSON.parse(content.slice(4, length + 4).toString()),
appId: content.slice(length + 4).toString()
}
}
exports.main = function(event, context) {
const {
signature: signature,
timestamp: timestamp,
nonce: nonce,
echostr: echostr
} = event.queryStringParameters
const tmpStr = getSignature('你设置的Token令牌', timestamp, nonce)
if (signature === tmpStr) {
return echostr
} else {
return
}
}
5)点击提交,如果出现Token令牌校验不通过,说明云函数那里没有设置好,是不是直接将上面代码拷贝,没有修改下面的令牌为自己设置的Token。
6)如果出现下图配置已完成,说明消息推送的工作已经完成了。
二、在“secCheckContent”云对象中新增图片验证的方法“imgSecCheck”
/* imgSecCheck({对象})
*picurls 客户端传url数组来
*openid 用户openid
*scene 场景值
*version 版本号
*quanzi_id 业务ID
*/
async imgSecCheck({picurls,openid="ozBCI62sKO1jZWxxH_nMoZQSYhHo",scene=2,version = 2,quanzi_id}={}){
const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin',
requestId: this.getUniCloudRequestId(), // 云函数内则写 context.requestId
});
//因为图片校验只能单图验证,所以将客户端传来的url数组循环遍历
for (let image of picurls) {
let res = await uniSecCheck.imgSecCheck({
image,
openid,
scene,
version
})
//将校验回调的唯一校验码traceId存储图片日志中
await db.collection("sec-check-img-log").add({
quanzi_id,
picurl:image,
traceId:res.traceId,
state:0,
publish_date:Date.now()
})
}
}
三、客户端发布圈子首页将文章状态改为0(不可见),提示发布成功后,调用云对象的imgSecCheck进行校验。
let quanzi_id = res.id;
let picurls = formData.value.picurls.map(item=>item.url);
if(picurls.length){
await secCheckObj.imgSecCheck({picurls,quanzi_id});
}
uni.showToast({
title:"发布成功,等待审核",
icon:"none",
mask:true
})
uni.navigateBack();
四、在消息推送云函数中(secCheckMsg),将返回的结果进行判断,将违规图删除,没有违规的修改文章状态为可见。下面是我业务详细代码,作为参考。
const crypto = require('crypto');
function getSignature(token, timestamp, nonce, msgEncrypt) {
const str = [token, timestamp, nonce, msgEncrypt].sort().join('')
return crypto.createHash('sha1').update(str).digest("hex")
}
function PKCS7Decode(buf) {
let padSize = buf[buf.length - 1]
return buf.slice(0, buf.length - padSize)
}
function decryptMsg(encodingAESKey, msgEncrypt) {
const key = Buffer.from(encodingAESKey + '=', 'base64')
const iv = key.slice(0, 16)
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
decipher.setAutoPadding(false)
let deciphered = Buffer.concat([decipher.update(msgEncrypt, 'base64'), decipher.final()])
deciphered = PKCS7Decode(deciphered)
const content = deciphered.slice(16)
const length = content.slice(0, 4).readUInt32BE(0)
return {
message: JSON.parse(content.slice(4, length + 4).toString()),
appId: content.slice(length + 4).toString()
}
}
exports.main = async function(event, context) {
const db = uniCloud.database();
const {
signature: signature,
timestamp: timestamp,
nonce: nonce,
echostr: echostr
} = event.queryStringParameters
let body = ''
if (event.body !== '') {
body = JSON.parse(event.body)
}
let result = body
const tmpStr = getSignature('你自己设置的Token', timestamp, nonce)
if (signature === tmpStr) {
// 验证是从微信发来的消息
if (body.Encrypt) {
const decrypt = decryptMsg('微信后台自动生成的秘钥', body.Encrypt);
//返回的所有数据
result = decrypt.message
//根据图片校验返回的审核ID,比对图片日志表,获取满足条件的数据,作为后续增删改查的依据
let imgLogs = await db.collection("sec-check-img-log").where({traceId:result.trace_id}).get();
let quanzi_id = imgLogs.data[0].quanzi_id;
let picurl = imgLogs.data[0].picurl;
//【重点】图片合规处理函数
if (result.result.suggest == 'pass') {
//根据图片日志返回的quanzi_id获取圈子表中对应指定的数据
let res = await db.collection("soup_quanzi").where({
_id:quanzi_id
}).get();
//修改图片状态,下面这段代码要是不想修改图片状态的话可以注释掉
await db.collection("sec-check-img-log").where({traceId:result.trace_id}).update({state:1});
//只用状态为0草稿箱的才能修改圈子状态,1通过的不再修改,-1不通过的也过滤掉
if(res.data[0].quanzi_status==0){
await db.collection("soup_quanzi").where({
_id:quanzi_id
}).update({
quanzi_status:1
});
}
}
//【重点】图片违规的处理函数
if (result.result.suggest == 'risky') {
//图片违规,立即将发布的圈子状态改为-1为审核不通过
await db.collection("soup_quanzi").where({
_id:quanzi_id
}).update({
quanzi_status:-1
});
//将图片日志的状态改为-1为不通过
await db.collection("sec-check-img-log").where({traceId:result.trace_id}).update({state:-1});
//删除违规图片,如果要看看用户传了什么,可以不删,但是占用存储空间,看个人选择,可以注释掉
await uniCloud.deleteFile({fileList: [picurl]})
}
}
return 'success'
} else {
return 'success'
}
}
图片校验完整的消息推送回调,下面代码给大家展示出来,分别是没有问题的图和违规图的返回值。
图片没有问题的返回值
{
"result": {
"ToUserName": "gh_ba616cbd6",
"FromUserName": "ozBCI62MZ0HWGNeZ2ce7lSWq8",
"CreateTime": 1695657534,
"MsgType": "event",
"Event": "wxa_media_check",
"appid": "wxbd9d0a676b6a4",
"trace_id": "651ae3a-5d4d44c-0f9e49e",
"version": 2,
"detail": [
{
"strategy": "content_model",
"errcode": 0,
"suggest": "pass",
"label": 100,
"prob": 90
}
],
"errcode": 0,
"errmsg": "ok",
"result": {
"suggest": "pass",
"label": 100
}
}
}
违规图的回调
{
"result": {
"ToUserName": "gh_4dbf781cf1",
"FromUserName": "o7ZWr5bHxZ0yMEt6-k_8RUU",
"CreateTime": 1693843523,
"MsgType": "event",
"Event": "wxa_media_check",
"appid": "wx3f7cf3d3a423a",
"trace_id": "64f603b-4a8bf26-05c372e",
"version": 2,
"detail": [
{
"strategy": "content_model",
"errcode": 0,
"suggest": "risky",
"label": 20002,
"prob": 90
}
],
"errcode": 0,
"errmsg": "ok",
"result": {
"suggest": "risky",
"label": 20002
}
}
}