关于小程序:小程序~内容安全

24次阅读

共计 6588 个字符,预计需要花费 17 分钟才能阅读完成。

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:

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





正文完
 0