iOS—APP被拒原因匯總

一、 iOS 端常見被拒原因匯總

  1. App 內(nèi)包含分發(fā)下載分發(fā)功能(引導(dǎo)用戶下載 App 等功能)。
  2. 提供的測(cè)試賬號(hào)無(wú)法查看實(shí)際功能
  3. 通過(guò)接口返回布爾值判斷 App 是否升級(jí),但審核期間該接口不請(qǐng)求
  4. 審核賬號(hào),任何時(shí)候在任何 ip 登錄看到的都是審核版。
  5. 提供的登陸賬號(hào)和密碼不對(duì),登陸不上
  6. 運(yùn)營(yíng)填寫的營(yíng)銷關(guān)鍵字有問(wèn)題
  7. 元數(shù)據(jù)問(wèn)題,iPhoneX 截圖中 iPhone 殼子是 iPhone7 的,應(yīng)該是 iPhoneX
  8. 說(shuō)明隱私權(quán)限的作用。
  9. 營(yíng)銷文字,某些能力需要資質(zhì)。此類功能在審核期間都關(guān)閉
  10. 修改隱私權(quán)限相關(guān)的文案,做到讓審核人員看得懂,做到「信達(dá)雅」
  11. App 無(wú)法登陸進(jìn)去,屬于 bug 級(jí)別
  12. App 沒(méi)有適配 ipad。
  13. Privacy - Data Collection and Storage,說(shuō)明 App 沒(méi)有做隱私權(quán)限的收集。
  14. 訪問(wèn) h5 頁(yè)面出現(xiàn)問(wèn)題。 屬于 bug 級(jí)別
  15. App 集成了設(shè)備指紋 SDK, 會(huì)上傳用戶設(shè)備安裝應(yīng)用列表。 解決:移除設(shè)備指紋SDK, 成功上架

二、 App 被拒原因匯總

從 Android 和 iOS 2端 App 被駁回的一些信息來(lái)看,駁回原因一般劃分為下面幾類:

  1. 審核期間,資源和配置都應(yīng)該調(diào)節(jié)為審核模式

  2. App 包含某些關(guān)鍵字

  3. 審核相關(guān)的元數(shù)據(jù)問(wèn)題(截圖與實(shí)際內(nèi)容不匹配、機(jī)型和截圖不匹配、提供給審核的賬號(hào)和密碼登陸不上)

  4. 使用的隱私權(quán)限必須說(shuō)明,文案描述必須清晰

  5. App 存在 bug (賬號(hào)無(wú)法登陸、沒(méi)有適配 ipad、訪問(wèn) h5 打不開 )

  6. 誘導(dǎo)用戶打開查看更多 App

  7. Android 應(yīng)用未加固

  8. 應(yīng)用缺乏相關(guān)的資質(zhì)和證書

    如想快速有效的提高iOS技術(shù)和能力,不防瞧瞧這一份iOS秘籍
    iOS提升武功秘籍

三、 方案

常見審核失敗的原因很多,很大比重一個(gè)就是代碼或者文本里面存在一些敏感詞,所以本文的側(cè)重點(diǎn)在于關(guān)鍵詞掃描。像上架設(shè)置的截圖和當(dāng)前設(shè)備不匹配、提供的賬號(hào)無(wú)法使用功能 ?? 這種情況打一頓就好了,非主流行為不在本文范圍內(nèi)

3.1 詞云誰(shuí)去收集?

每個(gè)公司一般來(lái)說(shuō)都不止一條業(yè)務(wù)線,所以每個(gè)業(yè)務(wù)線的 App 情況和內(nèi)容也不一樣,所以敏感詞也是千差萬(wàn)別。敏感詞收集這個(gè)事情,應(yīng)該由業(yè)務(wù)線主要負(fù)責(zé) App 的開發(fā)者來(lái)收集,根據(jù)平時(shí)的上架情況,蘋果的駁回的郵件來(lái)整理。

3.2 方案設(shè)計(jì)

公司自研工具 cli(iOS SDK、iOS App、Android SDK、Android App、RN、Node、React 依賴分析、構(gòu)建、打包、測(cè)試、熱修復(fù)、埋點(diǎn)、構(gòu)建),各個(gè)端都是通過(guò)「模版」來(lái)提供能力。包含若干子項(xiàng)目,每個(gè)子項(xiàng)目就是所謂的 “模版”,每個(gè)模版其實(shí)就是一個(gè) Node 工程,一個(gè) npm 模塊,主要負(fù)責(zé)以下功能:特定項(xiàng)目類型的目錄結(jié)構(gòu)、自定義命令供開發(fā)、構(gòu)建等使用、模版持續(xù)更新及 patch 等。

所以可以在打包構(gòu)建(各個(gè)端將項(xiàng)目提交到打包系統(tǒng),打包系統(tǒng)根據(jù)項(xiàng)目語(yǔ)言、平臺(tái)調(diào)度打包機(jī))的時(shí)候,拿到源代碼進(jìn)行掃描?;谶@個(gè)現(xiàn)狀,所以方案是「掃描是基于源代碼出發(fā)的掃描的」。

按照 iOS 端 pod install 這個(gè)過(guò)程,cocoapods 為我們預(yù)留了鉤子:PreInstallHook.rb、PostInstallHook.rb,允許我們?cè)诓煌碾A段為工程做一些自定義的操作,所以我們的 iOS 模版設(shè)計(jì)也參考了這個(gè)思想,在打包構(gòu)建前、構(gòu)建中、構(gòu)建后提供了鉤子:prebuild、build、postbuild。定位好了問(wèn)題,要做的就是在 prebuild 里面進(jìn)行關(guān)鍵詞掃描的編碼工作。

確定了什么時(shí)候做什么事情,接下來(lái)就要討論怎么做才合適。

3.3 技術(shù)方案選擇

字符串匹配算法 KMP 是一開始想到的內(nèi)容,針對(duì)某個(gè) App 進(jìn)行時(shí)機(jī)測(cè)試,發(fā)現(xiàn)50多個(gè)敏感詞的情況下,代碼掃描耗時(shí)60秒鐘,覺(jué)得非常不理想,看 KMP 算法沒(méi)有啥問(wèn)題,所以換個(gè)思路走下去。

因?yàn)槟0姹举|(zhì)上 Node 項(xiàng)目,所以 Node 下的 glob 模塊正好提供根據(jù)正則匹配到合適的文件,也可以匹配文件里面的字符串。然后繼續(xù)做實(shí)驗(yàn),數(shù)據(jù)如下:9個(gè)銘感詞語(yǔ)、代碼文件5967個(gè),耗時(shí)3.5秒

3.4 完整方案

  1. 業(yè)務(wù)線需要自定義敏感詞云(因?yàn)槊織l業(yè)務(wù)線的關(guān)鍵詞云都不一樣)
  2. 敏感詞需要?jiǎng)澐值燃?jí):error、warning。掃描到 error 需要馬上停止構(gòu)建,并提示「已掃描到你的源碼中存在敏感詞,可能存在提交審核失敗的可能,請(qǐng)修改后再次構(gòu)建」。warning 的情況不需要馬上停止構(gòu)建,等任務(wù)全部結(jié)束后匯總給出提示「已掃描到你的源碼中存在敏感詞、***...,可能存在提交審核失敗的可能,請(qǐng)開發(fā)者自己確認(rèn)」
  3. 銘感詞云的格式 scaner.yml 文件。
  • error: 數(shù)組的格式。后面寫需要掃描的關(guān)鍵詞,且等級(jí)為 error,表示掃描到 error 則馬上停止構(gòu)建
  • warning:數(shù)組的格式。后面寫需要掃描的關(guān)鍵詞,且等級(jí)為 warning,掃描結(jié)果不影響構(gòu)建,最終只是展示出來(lái)
  • searchPath:字符串格式??梢宰寴I(yè)務(wù)線自定義需要進(jìn)行掃描的路徑。
  • fileType:數(shù)組格式??梢宰寴I(yè)務(wù)線自定義需要掃描的文件類型。默認(rèn)為 sh|pch|json|xcconfig|mm|cpp|h|m
  • warningkeywordsScan:布爾值。業(yè)務(wù)線可以設(shè)置是否需要掃描 warning 級(jí)別的關(guān)鍵詞。
  • errorKeywordsScan:布爾值。業(yè)務(wù)線可以設(shè)置是否需要掃描 error 級(jí)別的關(guān)鍵詞。
error:
  - checkSwitch
warning:
  - loan
  - online
  - ischeck
searchPath:
  ../fixtures
fileType:
  - h
  - m
  - cpp
  - mm
  - js
warningkeywordsScan: true
errorKeywordsScan: true

  1. iOS 端存在私有 api 的情況,Android 端不存在該問(wèn)題 私有 api 70111個(gè)文件,每個(gè)文件假設(shè)10個(gè)方法,則共70萬(wàn)個(gè) api。所以計(jì)劃找出 top 100.去掃描匹配,支持業(yè)務(wù)線是否開啟的選項(xiàng)

其實(shí)這些問(wèn)題都是業(yè)界標(biāo)準(zhǔn)的做法,肯定需要預(yù)留這樣的能力,所以自定義規(guī)則的格式可以查看上面 yml 文件的各個(gè)字段所確定。明確了做什么事,以及做事情的標(biāo)準(zhǔn),那就可以很快的開展并落地實(shí)現(xiàn)。

'use strict'

const { Error, logger } = require('@company/BFF-utils')
const fs = require('fs-extra')
const glob = require('glob')
const YAML = require('yamljs')

module.exports = class PreBuildCommand {
  constructor(ctx) {
    this.ctx = ctx
    this.projectPath = ''
    this.fileNum = 0
    this.isExist = false
    this.errorFiles = []
    this.warningFiles = []
    this.keywordsObject = {}
    this.errorReg = null
    this.warningReg = null
    this.warningkeywordsScan = false
    this.errorKeywordsScan = false
    this.scanFileTypes = ''
  }

  async fetchCodeFiles(dirPath, fileType = 'sh|pch|json|xcconfig|mm|cpp|h|m') {
    return new Promise((resolve, reject) => {
      glob(`**/*.?(${fileType})`, { root: dirPath, cwd: dirPath, realpath: true }, (err, files) => {
        if (err) reject(err)
        resolve(files)
      })
    })
  }

  async scanConfigurationReader(keywordsPath) {
    return new Promise((resolve, reject) => {
      fs.readFile(keywordsPath, 'UTF-8', (err, data) => {
        if (!err) {
          let keywords = YAML.parse(data)
          resolve(keywords)
        } else {
          reject(err)
        }
      })
    })
  }

  async run() {
    const { argv } = this.ctx
    const buildParam = {
      scheme: argv.opts.scheme,
      cert: argv.opts.cert,
      env: argv.opts.env
    }

    // 處理包關(guān)鍵詞掃描(敏感詞匯 + 私有 api)
    this.keywordsObject = (await this.scanConfigurationReader(this.ctx.cwd + '/.scaner.yml')) || {}
    this.warningkeywordsScan = this.keywordsObject.warningkeywordsScan || false
    this.errorKeywordsScan = this.keywordsObject.errorKeywordsScan || false
    if (Array.isArray(this.keywordsObject.fileType)) {
      this.scanFileTypes = this.keywordsObject.fileType.join('|')
    }
    if (Array.isArray(this.keywordsObject.error)) {
      this.errorReg = this.keywordsObject.error.join('|')
    }
    if (Array.isArray(this.keywordsObject.warning)) {
      this.warningReg = this.keywordsObject.warning.join('|')
    }

    // 從指定目錄下獲取所有文件
    this.projectPath = this.keywordsObject ? this.keywordsObject.searchPath : this.ctx.cwd
    const files = await this.fetchCodeFiles(this.projectPath, this.scanFileTypes)

    if (this.errorReg && this.errorKeywordsScan) {
      await Promise.all(
        files.map(async file => {
          try {
            const content = await fs.readFile(file, 'utf-8')
            const result = await content.match(new RegExp(`(${this.errorReg})`, 'g'))
            if (result) {
              if (result.length > 0) {
                this.isExist = true
                this.fileNum++
                this.errorFiles.push(
                  `編號(hào): ${this.fileNum}, 所在文件: ${file},  出現(xiàn)次數(shù): ${result &&
                    (result.length || 0)}`
                )
              }
            }
          } catch (error) {
            throw error
          }
        })
      )
    }

    if (this.errorFiles.length > 0) {
      throw new Error(
        `從你的項(xiàng)目中掃描到了 error 級(jí)別的敏感詞,建議你修改方法名稱、屬性名、方法注釋、文檔描述。\n敏感詞有 「${
          this.errorReg
        }」\n存在問(wèn)題的文件有 ${JSON.stringify(this.errorFiles, null, 2)}`
      )
    }

    // warning
    if (this.warningReg && !this.isExist && this.fileNum === 0 && this.warningkeywordsScan) {
      await Promise.all(
        files.map(async file => {
          try {
            const content = await fs.readFile(file, 'utf-8')
            const result = await content.match(new RegExp(`(${this.warningReg})`, 'g'))
            if (result) {
              if (result.length > 0) {
                this.isExist = true
                this.fileNum++
                this.warningFiles.push(
                  `編號(hào): ${this.fileNum}, 所在文件: ${file},  出現(xiàn)次數(shù): ${result &&
                    (result.length || 0)}`
                )
              }
            }
          } catch (error) {
            throw error
          }
        })
      )

      if (this.warningFiles.length > 0) {
        logger.info(
          `從你的項(xiàng)目中掃描到了 warning 級(jí)別的敏感詞,建議你修改方法名稱、屬性名、方法注釋、文檔描述。\n敏感詞有 「${
            this.warningReg
          }」。有問(wèn)題的文件有${JSON.stringify(this.warningFiles, null, 2)}`
        )
      }
    }

    for (const key in buildParam) {
      if (!buildParam[key]) {
        throw new Error(`build: ${key} 參數(shù)缺失`)
      }
    }
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容