小程序~内容安全

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 api
promisifyAll(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.pro
export 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:


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
            })
        }
    }
}
        
        
    
    





你可能感兴趣的:(小程序)