# 内容安全

# 微信安全检测

微信官方提供了可疑用户与危险接口的检查:

image-20210815144637652

接口安全扫描:

image-20210815144649621

这两个功能在后台即可操作,平坦可以交由运营小姐姐来进行操作一下。

# 第三方内容安全推荐

# 文本内容安全

# 整体思路

  • 下载网络上的一些敏感词的数据库,使用正则进行匹配过滤一次,减少成本;
  • 使用官方的限额的api对文本进行查验;
  • 使用第三方的api对内容进行查验;

# 官方介绍

检查一段文本是否含有违法违规内容,下面介绍的接口:官方链接 (opens new window)

1.0 版本接口文档【点击查看】 (opens new window)

应用场景举例:

  1. 用户个人资料违规文字检测;
  2. 媒体新闻类用户发表文章,评论内容检测;
  3. 游戏类用户编辑上传的素材(如答题类小游戏用户上传的问题及答案)检测等。 频率限制:单个 appId 调用上限为 4000 次/分钟,2,000,000 次/天*

调用方式:

# 工具js封装

请求地址

POST https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN

请求参数

属性 类型 默认值 必填 说明
access_token / cloudbase_access_token string 接口调用凭证 (opens new window)
version string 接口版本号,2.0版本为固定值2
openid string 用户的openid(用户需在近两小时访问过小程序)
scene number 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志)
content string 需检测的文本内容,文本字数的上限为2500字
nickname string 用户昵称
title string 文本标题
signature string 个性签名,该参数仅在资料类场景有效(scene=1)

返回的 JSON 数据包

属性 类型 说明
errcode number 错误码
errmsg string 错误信息
trace_id string 唯一请求标识,标记单次请求
result object 综合结果
detail array 详细检测结果

逻辑:

  • 用户只用传文本内容,其他的openid等信息可以不传,设置一个默认值,方便其他的平台使用;
  • 使用正则匹配掉一些无用的内容;
  • 长度判断,分2500词进行多次判断;
  • 判断结果非pass的全部进入risky部分;
// 文本内容安全
// content - 内容
// title - 标题 可选
// signature - 签名 也是可选
export const wxMsgCheck = async (content, {
  user: {
    openid,
    name: nickname,
    remark: signature
  },
  scene,
  title
} = {
  user: {},
  scene: 3,
  title: ''
}) => {
  // POST https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN
  let accessToken = await wxGetAccessToken()
  let res
  try {
    // 1.过滤掉一些如Html,自定义的标签内容
    content = content.replace(/<[^>]+>/g, '').replace(/\sface\[\S{1,}]/g, '').replace(/img\[\S+\]/g, '').replace(/\sa\(\S+\]/g, '').replace(/\[\/?quote\]/g, '').replace(/\[\/?pre\]/g, '').replace(/\[\/?hr\]/g, '').replace(/[\r\n|\n|\s]/g, '')
    // 2.如果content内容超过了2500词,需要进行分段处理
    if (content.length > 2500) {
      // 分段 —> arr -> method1: for , method2: reg
      let arr = content.match(/[\s\S]{1,2500}/g) || []
      // 多次请求接口
      let mulResult = []
      for (let i = 0; i < arr.length; i++) {
        // 获取所有接口的返回结果 -> 结果判断 -> 返回
        res = await instance.post(`https://api.weixin.qq.com/wxa/msg_sec_check?access_token=${accessToken}`, {
          version: 2,
          openid: openid || 'ooTjn5YPpogMWLtEQ_PxyUJkIp2I',
          scene,
          content: arr[i],
          nickname: nickname,
          title,
          signature: scene === 1 ? signature : null
        })
        mulResult.push(res)
      }
      // 判断mulResult
      console.log(mulResult)
      const arrTemp = mulResult.filter(item => {
        const { status, data: { errcode, result } } = item
        return status !== 200 || errcode !== 0 || (result && result.suggest !== 'pass')
      })
      return !(arrTemp.length > 0)
    } else {
      res = await instance.post(`https://api.weixin.qq.com/wxa/msg_sec_check?access_token=${accessToken}`, {
        version: 2,
        openid: openid || 'ooTjn5YPpogMWLtEQ_PxyUJkIp2I',
        scene,
        content,
        nickname: nickname,
        title,
        signature: scene === 1 ? signature : null
      })
      const { status, data: { errcode, result } } = res
      return status === 200 && errcode === 0 && result && result.suggest === 'pass'
    }
  } catch (error) {
    logger.error(`wxMsgCheck error: ${error.message}`)
  }
}

# 自定义安全敏感词汇

在小程序管理后台,开发管理 -> 安全中心 -> 内容风控:

image-20210815144815314

在这里添加的分值越高,越可能被屏蔽。

# 测试接口工具js

  • 加入accessToken失效判断

    instance.interceptors.response.use(async (res) => {
      const { data } = res
      if (data.errcode === 40001) {
        // 重新获取新的accessToken
        const accessToken = await wxGetAccessToken(true)
        const { url } = res.config
        // 重新发起请求 -> res
        if (url.indexOf('access_token') !== -1) {
          const arr = url.split('?') // ?key=value&key1=value1... -> ['域名', 'key=value&key1=value1...']
          const params = qs.parse(arr[1])
          const newParams = {
            ...params,
            access_token: accessToken
          }
          const newUrl = arr[0] + '?' + qs.stringify(newParams)
          const config = { ...res.config, url: newUrl }
          const result = await axios(config)
          return result
        }
      }
      return res
    })
    
  • 有风险的词汇:

    image-20210815151921140

# 图片内容安全

# 整体思路

# 官方介绍

请求地址

POST https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN

请求参数

属性 类型 默认值 必填 说明
access_token / cloudbase_access_token string 接口调用凭证 (opens new window)
media FormData 要检测的图片文件,格式支持PNG、JPEG、JPG、GIF,图片尺寸不超过 750px x 1334px

返回的 JSON 数据包

属性 类型 说明
errcode number 错误码
errmsg string 错误信息

errcode 的合法值

说明 最低版本
0 内容正常
87014 内容可能潜在风险

# 工具js封装

安装依赖:

npm i make-dir form-date sharp del

其中sharp (opens new window)需要配置加速:

npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"

工具js:

// 获取头部属性
export 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)
    })
  })
}

// 删除文件
export const checkAndDelFile = async (path) => {
  try {
    accessSync(path, constants.R_OK | constants.W_OK)
    await del(path)
  } catch (err) {
    // console.error('no access!')
  }
}

// 图片内容安全
export const wxImgCheck = async (file) => {
  // POST https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
  const accessToken = await wxGetAccessToken()
  // 1.保证图片 -> 判断分辨率 -> sharp 750 * 1334
  let newPath = file.path
  const tmpPath = path.resolve('./tmp')
  try {
    const img = sharp(newPath)
    const meta = await img.metadata()
    if (meta.width > 750 || meta.height > 1334) {
      // 判断临时路径是否存在,并创建
      await mkdir(tmpPath)
      // uuid -> 指定临时的文件名称
      newPath = path.join(tmpPath, uuidv4() + path.extname(newPath) || '.jpg')
      await img.resize(750, 1334, {
        fit: 'inside'
      }).toFile(newPath)
    }
    const stream = fs.createReadStream(newPath)
    // 2.FormData类型的数据准备
    const form = new FormData()
    form.append('media', stream)
    const headers = await getHeaders(form)
    // 3.请求接口 -> 返回结果
    const result = await instance.post(`https://api.weixin.qq.com/wxa/img_sec_check?access_token=${accessToken}`, form, { headers })
    // 校验成功 -> 删除tmp数据 -> 判断路径中的文件是否存在
    console.log('🚀 ~ file: WxUtils.js ~ line 232 ~ wxImgCheck ~ result', result)
    await checkAndDelFile(newPath)
    return result.status === 200 && result.data && result.data.errcode === 0
    // if (result.status === 200 && result.data && result.data.errcode === 0) {
    //   // errcode 0 - 内容正常,否则 - 异常
    //   return true
    // } else {
    //   return false
    // }
  } catch (error) {
    await checkAndDelFile(newPath)
    logger.error(`wxImgCheck error: ${error.message}`)
  }
}

# 测试接口工具js

注意:

  • 自行找一下网上可疑的图片进行测试;
  • errcode为0说明校验通过,反之不通过;
  • 通过之后,删除图片临时目录中的文件;
  • 一般上传图片的接口需要对接云上的存储,所以采用了本地缓存的方式校验图片;

image-20210815154746020