前言
相比於文字的安全檢測,圖片的安全檢測要稍微略複雜一些,當您讀完本篇,將get到
- 圖片安全檢測的應用場景
- 解決圖片的安全校驗的方式
- 使用雲呼叫方式對圖片進行檢測
- 如何對上傳圖片大小進行限制
- 如何解決多圖上傳覆蓋問題
示例效果
當使用者上傳敏感違規圖片時,禁止使用者上傳發布,並且做出相對應的使用者友好提示
應用場景
通常,在校驗一張圖片是否含有違法違規內容相比於文字安全的校驗,同樣重要,有如下應用
- 圖片智慧鑑黃:涉及拍照的工具類應用(如美拍,識圖類應用)使用者拍照上傳檢測;電商類商品上架圖片檢測;媒體類使用者文章裡的圖片檢測等
- 敏感人臉識別:使用者頭像;媒體類使用者文章裡的圖片檢測;社交類使用者上傳的圖片檢測等,凡是有使用者自發生產內容的都應當提前做檢測
解決圖片的安全手段
在小程式開發中,提供了兩種方式
- HTTPS呼叫
- 雲呼叫
HTTPS 呼叫的請求介面地止
https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
檢測圖片稽核,根據官方文件得知,需要兩個必傳的引數:分別是:access_token(介面呼叫憑證),media(要檢測的圖片檔案)
對於HTTPS呼叫方式,願意折騰的小夥伴可以參考文字內容安全檢測(上篇)的處理方式,處理大同小異,本篇主要以雲開發的雲呼叫為主
功能實現:小程式端邏輯
對於wxml與wxss,大家可以自行任意修改,本文重點在於圖片安全的校驗
<view class="image-list">
<!-- 顯示圖片 -->
<block wx:for="{{images}}" wx:key="*this"><view class="image-wrap">
<image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i></view>
</block>
<!-- 選擇圖片 -->
<view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
</view>
<view class="footer"><button class="send-btn" bind:tap="send">釋出</button>
</view>
對應的wxss程式碼
.footer {
display: flex;
align-items: center;
width: 100%;
box-sizing: border-box;
background: #34bfa3;
}
.send-btn {
width: 100%;
color: #fff;
font-size: 32rpx;
background: #34bfa3;
}
button {
border-radius: 0rpx;
}
button::after {
border-radius: 0rpx !important;
}
/* 圖片樣式 */
.image-list {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
}
.image-wrap {
width: 220rpx;
height: 220rpx;
margin-right: 10rpx;
margin-bottom: 10rpx;
position: relative;
overflow: hidden;
text-align: center;
}
.image {
width: 100%;
height: 100%;
}
.icon-shanchu {
position: absolute;
top: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background-color: #000;
opacity: 0.4;
color: #fff;
text-align: center;
line-height: 40rpx;
font-size: 38rpx;
font-weight: bolder;
}
.selectphoto {
border: 2rpx dashed #cbd1d7;
position: relative;
}
.icon-add {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #cbd1d7;
font-size: 60rpx;
}
最終呈現的UI,由於只是用於圖片檢測演示,UI方面可忽略,如下所示
對應的JS程式碼
/*
* 涉及到的API:wx.chooseImage 從本地相簿選擇圖片或使用相機拍照
*(https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html)
*
*
*/// 最大上傳圖片數量
const MAX_IMG_NUM = 9;
const db = wx.cloud.database(); // 初始化雲資料庫
Page({
/**
* 頁面的初始資料
*/
data: {
images: [], // 把上傳的圖片存放在一個陣列物件裡面
selectPhoto: true, // 新增+icon元素是否顯示
},
/**
* 生命週期函式--監聽頁面載入
*/
onLoad: function (options) {
},
// 選擇圖片
onChooseImage() {
// 還能再選幾張圖片,初始值設定最大的數量-當前的圖片的長度
let max = MAX_IMG_NUM - this.data.images.length;
wx.chooseImage({
count: max, // count表示最多可以選擇的圖片張數
sizeType: ['original', 'compressed'], // 所選的圖片的尺寸
sourceType: ['album', 'camera'], // 選擇圖片的來源
success: (res) => { // 介面呼叫成功的回撥函式console.log(res)
this.setData({ // tempFilePath可以作為img標籤的src屬性顯示圖片,下面是將後新增的圖片與之前的圖片給追加起來
images: this.data.images.concat(res.tempFilePaths)
})
// 還能再選幾張圖片
max = MAX_IMG_NUM - this.data.images.length
this.setData({
selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
})
},
})
},
// 點選右上方刪除圖示,刪除圖片操作
onDelImage(event) {
const index = event.target.dataset.index;
// 點選刪除當前圖片,用splice方法,刪除一張,從陣列中移除一個
this.data.images.splice(index, 1)
this.setData({
images: this.data.images
})
// 當新增的圖片達到設定最大的數量時,新增按鈕隱藏,不讓新新增圖片
if (this.data.images.length == MAX_IMG_NUM - 1) {
this.setData({
selectPhoto: true,
})
}
},
})
最終實現的前端UI效果如下所是:
您現在看到的效果,沒有任何雲函式程式碼,只是前端的純靜態展示,對於一些涉嫌敏感圖片,是有必要進行做過濾處理的
功能實現:雲函式側邏輯
在cloudfunctions目錄資料夾下建立雲函式imgSecCheck
並在該目錄下建立config.json,配置引數如下所示
{
"permissions": {
"openapi": [
"security.imgSecCheck"
]
}
}
配置完後,在主入口index.js中,如下所示,通過security.imgSecCheck介面,並傳入media物件
// 雲函式入口檔案
const cloud = require('wx-server-sdk');
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
// 雲函式入口函式
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
try {
const result = await cloud.openapi.security.imgSecCheck({
media: {
contentType: 'image/png',
value: Buffer.from(event.img) // 這裡必須要將小程式端傳過來的進行Buffer轉化,否則就會報錯,介面異常
}
})
if (result && result.errCode.toString() === '87014') {
return { code: 500, msg: '內容含有違法違規內容', data: result }
} else {
return { code: 200, msg: '內容ok', data: result }
}
} catch (err) {
// 錯誤處理
if (err.errCode.toString() === '87014') {
return { code: 500, msg: '內容含有違法違規內容', data: err }
}
return { code: 502, msg: '呼叫imgSecCheck介面異常', data: err }
}
}
您會發現在雲函式端,就這麼幾行程式碼,就完成了圖片安全校驗
而在小程式端,程式碼如下所示
// miniprogram/pages/imgSecCheck/imgSecCheck.js
// 最大上傳圖片數量
const MAX_IMG_NUM = 9;
const db = wx.cloud.database()
Page({
/**
* 頁面的初始資料
*/
data: {
images: [],
selectPhoto: true, // 新增圖片元素是否顯示
},
/**
* 生命週期函式--監聽頁面載入
*/
onLoad: function (options) {
},
// 選擇圖片
onChooseImage() {
// const that = this; // 如果下面用了箭頭函式,那麼這行程式碼是不需要的,直接用this就可以了的// 還能再選幾張圖片,初始值設定最大的數量-當前的圖片的長度
let max = MAX_IMG_NUM - this.data.images.length;
wx.chooseImage({
count: max,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => { // 這裡若不是箭頭函式,那麼下面的this.setData的this要換成that上面的臨時變數,作用域的問題,不清楚的,可以看下this指向相關的知識
console.log(res)
// tempFilePath可以作為img標籤的src屬性顯示圖片
const tempFiles = res.tempFiles;
this.setData({
images: this.data.images.concat(res.tempFilePaths)
})
// 在選擇圖片時,對本地臨時儲存的圖片,這個時候,進行圖片的校驗,當然你放在最後點選發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程式端請求雲函式方式// 圖片轉化buffer後,呼叫雲函式
console.log(tempFiles);
tempFiles.forEach(items => {
console.log(items);
// 圖片轉化buffer後,呼叫雲函式
wx.getFileSystemManager().readFile({
filePath: items.path,
success: res => {
console.log(res);
wx.cloud.callFunction({ // 小程式端請求imgSecCheck雲函式,並傳遞img引數進行檢驗
name: 'imgSecCheck',
data: {
img: res.data
}
})
.then(res => {
console.log(res);
let { errCode } = res.result.data;
switch(errCode) {
case 87014:
this.setData({
resultText: '內容含有違法違規內容'
})
break;
case 0:
this.setData({
resultText: '內容OK'
})
break;
default:
break;
}
})
.catch(err => {
console.error(err);
})
},
fail: err => {
console.error(err);
}
})
})
// 還能再選幾張圖片
max = MAX_IMG_NUM - this.data.images.length
this.setData({
selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
})
},
})
},
// 刪除圖片
onDelImage(event) {
const index = event.target.dataset.index;
// 點選刪除當前圖片,用splice方法,刪除一張,從陣列中移除一個
this.data.images.splice(index, 1);
this.setData({
images: this.data.images
})
// 當新增的圖片達到設定最大的數量時,新增按鈕隱藏,不讓新新增圖片
if (this.data.images.length == MAX_IMG_NUM - 1) {
this.setData({
selectPhoto: true,
})
}
},
})
示例效果如下所示:
至此,關於圖片安全檢測就已經完成了,您只需要根據檢測的結果,做一些友好的使用者提示,或者做一些自己的業務邏輯判斷即可
常見問題
如何對上傳的圖片大小進行限制
有時候,您需要對使用者上傳圖片的大小進行限制,限制使用者任意上傳超大圖片,那怎麼處理呢,在微信小程式裡面,主要藉助的是wx.chooseImage這個介面成功返回後臨時路徑的res.tempFiles中的size大小判斷即可進行處理
具體例項程式碼如下所示
// 選擇圖片
onChooseImage() {
// 還能再選幾張圖片,初始值設定最大的數量-當前的圖片的長度
let max = MAX_IMG_NUM - this.data.images.length;
wx.chooseImage({
count: max,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
console.log(res)
const tempFiles = res.tempFiles;
this.setData({
images: this.data.images.concat(res.tempFilePaths) // tempFilePath可以作為img標籤的src屬性顯示圖片
})
// 在選擇圖片時,對本地臨時儲存的圖片,這個時候,進行圖片的校驗,當然你放在最後點選發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程式端請求雲函式方式// 圖片轉化buffer後,呼叫雲函式
console.log(tempFiles);
tempFiles.forEach(items => {
if (items && items.size > 1 * (1024 * 1024)) { // 限制圖片的大小
wx.showToast({
icon: 'none',
title: '上傳的圖片超過1M,禁止使用者上傳',
duration: 4000
})
// 超過1M的圖片,禁止使用者上傳
}
console.log(items);
// 圖片轉化buffer後,呼叫雲函式
wx.getFileSystemManager().readFile({
filePath: items.path,
success: res => {
console.log(res);
wx.cloud.callFunction({ // 請求呼叫雲函式imgSecCheck
name: 'imgSecCheck',
data: {
img: res.data
}
})
.then(res => {
console.log(res);
let { errCode } = res.result.data;
switch(errCode) {
case 87014:
this.setData({
resultText: '內容含有違法違規內容'
})
break;
case 0:
this.setData({
resultText: '內容OK'
})
break;
default:
break;
}
})
.catch(err => {
console.error(err);
})
},
fail: err => {
console.error(err);
}
})
})
// 還能再選幾張圖片
max = MAX_IMG_NUM - this.data.images.length
this.setData({
selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
})
},
})
},
注意: 使用微信官方的圖片內容安全介面進行校驗,限制圖片大小限制:1M,否則的話就會報錯
也就是說,對於超過1M大小的違規圖片,微信官方提供的這個圖片安全介面是無法進行校驗的
這個根據自己的業務而定,在小程式端對使用者上傳圖片的大小進行限制如果您覺得微信官方提供的圖片安全介面滿足不了自己的業務需求,那麼可以選擇一些其他的圖片內容安全校驗的介面的
這個圖片安全校驗是非常有必要的,使用者一旦上傳非法圖片,一旦通過網路進行傳播,產生了社會影響,平臺是有責任的,這種前車之鑑是有的
如何解決多圖上傳覆蓋的問題
對於上傳圖片來說,這個wx.cloud.uploadFileAPI介面只能上傳一張圖片,但是很多時候,是需要上傳多張圖片到雲端儲存當中的,當點選發布的時候,我們是希望將多張圖片都上傳到雲端儲存當中去的
這個API雖然只能每次上傳一張,但您可以迴圈遍歷多張圖片,然後一張一張的上傳的
在cloudPath上傳檔案的引數當中,它的值:需要注意:檔案的名稱
那如何保證上傳的圖片不被覆蓋,檔案不重名的情況下就不會被覆蓋
而在選擇圖片的時候,不應該上傳,因為使用者可能有刪除等操作,如果直接上傳的話會造成資源的浪費
而應該在點發布按鈕的時候,才執行上傳操作,檔案不重名覆蓋的示例程式碼如下所示
let promiseArr = []
let fileIds = [] // 將圖片的fileId存放到一個陣列中
let imgLength = this.data.images.length;
// 圖片上傳
for (let i = 0; i < imgLength; i++) {
let p = new Promise((resolve, reject) => {
let item = this.data.images[i]
// 副檔名
let suffix = /\.\w+$/.exec(item)[0]; // 取檔案後擴充名
wx.cloud.uploadFile({ // 利用官方提供的上傳介面
cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix, // 雲端儲存路徑,您也可以使用es6中的模板字串進行拼接的
filePath: item, // 要上傳檔案資源的路徑
success: (res) => {
console.log(res);
console.log(res.fileID)
fileIds = fileIds.concat(res.fileID) // 將新上傳的與之前上傳的給拼接起來
resolve()
},
fail: (err) => {
console.error(err)
reject()
}
})
})
promiseArr.push(p)
}
// 存入到雲資料庫,其中這個Promise.all(),等待裡面所有的任務都執行之後,在去執行後面的任務,也就是等待上傳所有的圖片上傳完後,才能把相對應的資料存到資料庫當中,具體與promise相關問題,可自行查漏
Promise.all(promiseArr).then((res) => {
db.collection('blog').add({ // 查詢blog集合,將img,時間等資料新增到這個集合當中
data: {
img: fileIds,
createTime: db.serverDate(), // 服務端的時間
}
}).then((res) => {
console.log(res);
this._hideToastTip();
this._successTip();
})
})
.catch((err) => {
// 釋出失敗console.error(err);
})
上面通過利用當前時間+隨機數的方式進行了一個區分,規避了上傳檔案同名的問題
因為這個上傳介面,一次性只能上傳一張圖片,所以需要迴圈遍歷圖片,然後一張張的上傳
一個是上傳到雲端儲存中,另一個是新增到雲資料庫集合當中,要分別注意下這兩個操作,雲資料庫中的圖片是從雲端儲存中拿到的,然後再新增到雲資料庫當中去的
示例效果如下所示:
將上傳的圖片儲存到雲資料庫中
注意:新增資料到雲資料庫中,需要手動建立集合,不然是無法上傳不到雲資料庫當中的,會報錯
至此,關於敏感圖片的檢測,以及多圖片的上傳到這裡就已經完成了
如下是完整的小程式端邏輯示例程式碼
// miniprogram/pages/imgSecCheck/imgSecCheck.js
// 最大上傳圖片數量
const MAX_IMG_NUM = 9;
const db = wx.cloud.database()
Page({
/**
* 頁面的初始資料
*/
data: {
images: [],
selectPhoto: true, // 新增圖片元素是否顯示
},
/**
* 生命週期函式--監聽頁面載入
*/
onLoad: function (options) {
},
// 選擇圖片
onChooseImage() {
// 還能再選幾張圖片,初始值設定最大的數量-當前的圖片的長度
let max = MAX_IMG_NUM - this.data.images.length;
wx.chooseImage({
count: max,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
console.log(res)
const tempFiles = res.tempFiles;
this.setData({
images: this.data.images.concat(res.tempFilePaths) // tempFilePath可以作為img標籤的src屬性顯示圖片
})
// 在選擇圖片時,對本地臨時儲存的圖片,這個時候,進行圖片的校驗,當然你放在最後點選發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程式端請求雲函式方式
// 圖片轉化buffer後,呼叫雲函式
console.log(tempFiles);
tempFiles.forEach(items => {
if (items && items.size > 1 * (1024 * 1024)) {
wx.showToast({
icon: 'none',
title: '上傳的圖片超過1M,禁止使用者上傳',
duration: 4000
})
// 超過1M的圖片,禁止上傳
}
console.log(items);
// 圖片轉化buffer後,呼叫雲函式
wx.getFileSystemManager().readFile({
filePath: items.path,
success: res => {
console.log(res);
this._checkImgSafe(res.data); // 檢測圖片安全校驗
},
fail: err => {
console.error(err);
}
})
})
// 還能再選幾張圖片
max = MAX_IMG_NUM - this.data.images.length
this.setData({
selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
})
},
})
},
// 刪除圖片
onDelImage(event) {
const index = event.target.dataset.index;
// 點選刪除當前圖片,用splice方法,刪除一張,從陣列中移除一個
this.data.images.splice(index, 1);
this.setData({
images: this.data.images
})
// 當新增的圖片達到設定最大的數量時,新增按鈕隱藏,不讓新新增圖片
if (this.data.images.length == MAX_IMG_NUM - 1) {
this.setData({
selectPhoto: true,
})
}
},
// 點選發布按鈕,將圖片上傳到雲資料庫當中
send() {
const images = this.data.images.length;
if (images) {
this._showToastTip();
let promiseArr = []
let fileIds = []
let imgLength = this.data.images.length;
// 圖片上傳
for (let i = 0; i < imgLength; i++) {
let p = new Promise((resolve, reject) => {
let item = this.data.images[i]
// 副檔名
let suffix = /\.\w+$/.exec(item)[0]; // 取檔案後擴充名
wx.cloud.uploadFile({ // 上傳圖片至雲端儲存,迴圈遍歷,一張張的上傳
cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
filePath: item,
success: (res) => {
console.log(res);
console.log(res.fileID)
fileIds = fileIds.concat(res.fileID)
resolve()
},
fail: (err) => {
console.error(err)
reject()
}
})
})
promiseArr.push(p)
}
// 存入到雲資料庫
Promise.all(promiseArr).then((res) => {
db.collection('blog').add({ // 查詢blog集合,將資料新增到這個集合當中
data: {
img: fileIds,
createTime: db.serverDate(), // 服務端的時間
}
}).then((res) => {
console.log(res);
this._hideToastTip();
this._successTip();
})
})
.catch((err) => {
// 釋出失敗
console.error(err);
})
} else {
wx.showToast({
icon: 'none',
title: '沒有選擇任何圖片,釋出不了',
})
}
},
// 校驗圖片的安全
_checkImgSafe(data) {
wx.cloud.callFunction({
name: 'imgSecCheck',
data: {
img: data
}
})
.then(res => {
console.log(res);
let {
errCode
} = res.result.data;
switch (errCode) {
case 87014:
this.setData({
resultText: '內容含有違法違規內容'
})
break;
case 0:
this.setData({
resultText: '內容OK'
})
break;
default:
break;
}
})
.catch(err => {
console.error(err);
})
},
_showToastTip() {
wx.showToast({
icon: 'none',
title: '釋出中...',
})
},
_hideToastTip() {
wx.hideLoading();
},
_successTip() {
wx.showToast({
icon: 'none',
title: '釋出成功',
})
},
})
完整的示例wxml,如下所示
<view class="image-list">
<!-- 顯示圖片 -->
<block wx:for="{{images}}" wx:key="*this">
<view class="image-wrap"><image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i>
</view>
</block>
<!-- 選擇圖片 -->
<view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
</view>
<view class="footer">
<button class="send-btn" bind:tap="send">釋出</button>
</view>
<view>
檢測結果顯示: {{ resultText }}
</view>
您可以根據自己的業務邏輯需要,一旦檢測到圖片違規時,禁用按鈕狀態,或者給一些使用者提示,都是可以的,在釋出之前或者點選發布時,進行圖片內容安全的校驗都可以,一旦發現圖片有違規時,就不讓繼續後面的操作的
結語
本文主要通過藉助官方提供的圖片security.imgSecCheck
介面,實現了對圖片安全的校驗,實現起來,是相當的方便的,對於基礎性的校驗,利用官方提供的這個介面,已經夠用了的,但是如果想要更加嚴格的檢測,可以引入一些第三方的內容安全強強校驗,確保內容的安全
實現瞭如何對上傳的圖片大小進行限制,以及解決同名圖片上傳覆蓋的問題
如果大家對文字內容安全校驗以及圖片安全校驗仍然有什麼問題,可以在下方留言,一起探討。
公眾號:騰訊云云開發
騰訊云云開發:https://cloudbase.net
雲開發控制檯:https://console.cloud.tencent.com/tcb?from=12304
☁
更多精彩
掃描二維碼瞭解更多