1:背景:

对于小程序端或者其余端的ugc(用户产生的文本内容[文本、图片...])是须要退出内容的平安校验的。参考链接(https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html)

2:利用场景:

1: 文本    查看一段文本是否含有守法违规内容    用户个人资料违规文字检测    媒体新闻类用户发表文章,评论内容检测    游戏类用户编辑上传的素材等2: 图片    校验一张图片是否含有守法违规内容    图片智能鉴黄    敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等e

3: 接口对接(https):

3.1: config的配置    const REDIS = {      host: process.env.REDIS_HOST || 'www.exapmle.com',      port: process.env.REDIS_PORT || 'xxxx',      password: process.env.REDIS_PASS || 'xxxxxxxx'    }    const AppID = 'xxxxxxxxxxxx'    const AppSecret = 'xxxxxxxxxxxx'    export {        REDIS,        AppID,        AppSecret    }3.2: redis的配置(redisConfig)    import redis from 'redis'    import { promisifyAll } from 'bluebird'    import config from './index'    const options = {        host: config.REDIS.host,        port: config.REDIS.port,        password: config.REDIS.password,        detect_buffers: true,        retry_strategy: function (options) {            if (options.error && options.error.code === 'ECONNREFUSED') {                return new Error('The server refused the connection')            }            if (options.total_retry_time > 1000 * 60 * 60) {                return new Error('Retry time exhausted')            }            if (options.attempt > 10) {                return undefined            }            return Math.min(options.attempt * 100, 3000)        }    }    let client = promisifyAll(redis.createClient(options))    client.on('error', (err) => {        console.log('Redis Client Error:' + err)    })    const setValue = (key, value, time) => {        if (!client.connected) {            client = promisifyAll(redis.createClient(options))        }        if (typeof value === 'undefined' || value == null || value === '') {            return        }        if (typeof value === 'string') {        if (typeof time !== 'undefined') {            client.set(key, value, 'EX', time, (err, result) => {        })        } else {            client.set(key, value)        }        } else if (typeof value === 'object') {            Object.keys(value).forEach((item) => {            })        }    }    const getValue = (key) => {      if (!client.connected) {        client = promisifyAll(redis.createClient(options))      }      return client.getAsync(key)    }    export {        setValue,        getValue    }3.3: 获取小程序全局惟一后盾接口调用凭据(`access_token`)    import axios from 'axios'    import { getValue, setValue } from 'redisConfig'    import config from 'config'    export const wxGetAccessToken = async (flag = false) => {        let accessToken = await getValue('accessToken')        if (!accessToken || flag) {            const result = await axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.AppID}&secret=${config.AppSecret}`)            if (result.data === 200) {                await setValue('accessToken', result.data.access_token, result.data.expires_in)                accessToken = result.data.access_token                if (result.data.errcode && result.data.errmsg) {                    logger.error(`Wx-GetAccessToken Error: ${result.data.errcode} - ${result.data.errmsg}`)                }            }          }        return accessToken    }3.4: 内容平安    export const wxMsgCheck = async (content) => {        const accessToken = await wxGetAccessToken()        try {            const result = await axios.post(`https://api.weixin.qq.com/wxa/msg_sec_check?access_token=${accessToken}`, { content })            if (result.status === 200) {                return result.data            } else {                logger.error(`wxMsgCheck Error: ${result.statis}`)            }        } catch (error) {            logger.error(`wxMsgCheck Error: ${error.message}`)        }    }3.5: 文本平安校验    import { wxMsgCheck } from 'WxUtils'    async addWxPost (ctx) {        const { body } = ctx.request        const content = body.content        const result = await wxMsgCheck(content)        ...    }3.6: 图片平安校验?    3.6.1: 文件目录查看        import fs from 'fs'        import path from 'path'        const getStats = (path) => {            return new Promise (resolve => {                fs.stat(path, (err, stats) => err ? resolve(false) : resolve(stats))            })        }        const mkdir = (dir) => {            return new Promise((resolve) => {                    fs.mkdir(dir, err => err ? resolve(false) : resolve(true))            }        }        const dirExists = async (dir) => {          const isExists = await getStats(dir)          // 如果该门路存在且不是文件,返回 true          if (isExists && isExists.isDirectory()) {            return true          } else if (isExists) {            // 门路存在,然而是文件,返回 false            return false          }          // 如果该门路不存在          const tempDir = path.parse(dir).dir          // 循环遍历,递归判断如果下级目录不存在,则产生下级目录          const status = await dirExists(tempDir)          if (status) {            const result = await mkdir(dir)            console.log('TCL: dirExists -> result', result)            return result          } else {            return false          }        }        const getHeaders = (form) => {          return new Promise((resolve, reject) => {            form.getLength((err, length) => {              if (err) {                reject(err)              }              const headers = Object.assign(                { 'Content-Length': length },                form.getHeaders()              )              resolve(headers)            })          })        }      3.6.2: 图片内容校验        import fs from 'fs'        import path from 'path'        import del from 'del'        import { dirExists } from '/Utils'        import { v4 as uuidv4 } from 'uuid'        import sharp from 'sharp'        import FormData from 'form-data'        import pathExists from 'path-exists'        export const wxImgCheck = async (file) => {            const accessToken = await wxGetAccessToken()            let newPath = file.path            const tmpPath = path.resolve('./tmp')            try {                 // 1.筹备图片的form-data                 // 2.解决图片 - 要检测的图片文件,格局反对PNG、JPEG、JPG、GIF,图片尺寸不超过 750px x 1334px                 const img = sharp(file.path)                 const meta = await img.metadata() // 分辨率                 if (meta.width > 750 || meta.height > 1334) {                    await dirExists(tmpPath)                    newPath = path.join(tmpPath, uuidv4() + '.png')                    await img.resize(750, 1334, {                        fit: 'inside'                    }).toFile(newPath)                 }                 const stream = fs.createReadStream(newPath)                 const form = new FormData()                 form.append('media', stream)                 const headers = await getHeaders(form)                     const result = await axios.post(`https://api.weixin.qq.com/wxa/img_sec_check?access_token=${accessToken}`, form, { headers })                 const stats = await pathExists(newPath)                 if (stats) {                    await del([tmpPath + path.extname(newPath)], { force: true })                 }                if (result.status === 200) {                  if (result.data.errcode !== 0) {                    await wxGetAccessToken(true)                    await wxImgCheck(file)                    return                  }                  return result.data                } else {                  logger.error(`wxMsgCheck Error: ${result.statis}`)                }            } catch (error) {                const stats = await pathExists(newPath)                if (stats) {                    await del([tmpPath + path.extname(newPath)], { force: true })                }                logger.error(`wxMsgCheck Error: ${error.message}`)            }        }   3.6.3:       import { wxImgCheck } from '/WxUtils'    async uploadImg (ctx) {        const file = ctx.request.files.file        const result = await wxImgCheck(file)        ...    }    

4: web

4.1: config    export default {      baseUrl: {        dev: 'http://xxx.xxx.xx.xxx:3000',        pro: 'http://api.xxx.xxx.com:22000'      },      publicPath: [/^\/public/, /^\/login/]    }

4.2: wx

import { promisifyAll } from 'miniprogram-api-promise'const wxp = {}// promisify all wx's apipromisifyAll(wx, wxp)export default wxp

4.3: wx.store

import wx from './wx'class Storage {  constructor (key) {    this.key = key  }  async set (data) {    const result = await wx.setStorage({      key: this.key,      data: data    })    return result  }  async get () {    let result = ''    try {      result = await wx.getStorage({ key: this.key })    } catch (error) {      console.log('Storage -> get -> error', error)    }    return result.data ? result.data : result  }}const StoreToken = new Storage('token')export { Storage, StoreToken }

4.4: upload.js

import config from 'config'import wx from '/wx'import { StoreToken } from '/wxstore'const baseUrl =  process.env.NODE_ENV === 'development'    ? config.baseUrl.dev    : config.baseUrl.proexport const uploadImg = async (file) => {  try {    const token = await StoreToken.get()    const upTask = await wx.uploadFile({      url: baseUrl + '/content/upload',      filePath: file.path,      name: 'file',      header: {        'Authorization': 'Bearer ' + token      },      formData: {        file      }    })    if (upTask.statusCode === 200) {      return JSON.parse(upTask.data)    }  } catch (error) {    console.log('UploadImg -> error', error)  }}

4.5:

<van-uploader @afterRead="afterRead" :fileList="fileList"></van-uploader>async afterRead (e) {    const file = e.mp.detail.file    uploadImg(file).then((res) => {        if (res.code === 200) {            this.fileList.push(file)            wx.showToast({                title: '上传胜利',                icon: 'none',                duraction: 2000            })        }    }}