HarmonyOS APP开发:文本分类与内容审核

HarmonyOS APP开发:文本分类与内容审核

核心要点:本文深入讲解HarmonyOS平台上的文本分类与内容审核技术实现,涵盖基于规则引擎的快速分类、基于NLP模型的智能分类、多级内容审核流水线设计,以及在ArkTS中的完整工程实践。


一、背景与动机

想象一下这个场景:你正在开发一款社区类APP,用户每天产生数万条评论、帖子和私信。如果没有内容审核机制,垃圾信息、违规内容、恶意广告就会像洪水一样涌入,把你的社区变成一个无人愿意停留的"垃圾场"。

更现实的问题是------各大应用市场上架审核越来越严格,如果你的APP没有内容安全机制,轻则被拒审,重则被下架。这不是危言耸听,而是无数开发者用真金白银换来的教训。

文本分类和内容审核,本质上就是给文字"贴标签"和"把门"的过程。分类是理解"这段话在说什么",审核是判断"这段话能不能说"。两者结合,才能构建一个既安全又有温度的内容生态。

HarmonyOS提供了强大的NLP基础能力,包括文本分类、实体识别、情感分析等,配合端侧AI推理框架,我们可以在设备端完成大部分分类和审核工作,既保护了用户隐私,又降低了服务端压力。这,就是今天我们要聊的核心话题。


二、核心原理

2.1 文本分类的技术体系

文本分类从方法论上可以分为三大路线:

  • 基于规则引擎:用正则表达式、关键词匹配、模式识别进行快速分类。速度快、可解释性强,但覆盖面有限。
  • 基于传统机器学习:TF-IDF + 朴素贝叶斯/SVM,需要特征工程,适合中小规模场景。
  • 基于深度学习:CNN/RNN/Transformer等模型,端到端学习,准确率高,但计算开销大。

在HarmonyOS端侧,我们推荐"规则引擎 + 轻量模型"的混合架构------规则引擎处理明确的、模式化的内容,轻量模型处理模糊的、需要语义理解的内容。这样既保证了速度,又兼顾了准确率。

2.2 内容审核的多级流水线

内容审核不是一步到位的,而是一个多级过滤的流水线:
#mermaid-svg-SMk8tjbTAM3dfX47{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SMk8tjbTAM3dfX47 .error-icon{fill:#552222;}#mermaid-svg-SMk8tjbTAM3dfX47 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SMk8tjbTAM3dfX47 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SMk8tjbTAM3dfX47 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SMk8tjbTAM3dfX47 .marker.cross{stroke:#333333;}#mermaid-svg-SMk8tjbTAM3dfX47 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SMk8tjbTAM3dfX47 p{margin:0;}#mermaid-svg-SMk8tjbTAM3dfX47 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster-label text{fill:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster-label span{color:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster-label span p{background-color:transparent;}#mermaid-svg-SMk8tjbTAM3dfX47 .label text,#mermaid-svg-SMk8tjbTAM3dfX47 span{fill:#333;color:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 .node rect,#mermaid-svg-SMk8tjbTAM3dfX47 .node circle,#mermaid-svg-SMk8tjbTAM3dfX47 .node ellipse,#mermaid-svg-SMk8tjbTAM3dfX47 .node polygon,#mermaid-svg-SMk8tjbTAM3dfX47 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SMk8tjbTAM3dfX47 .rough-node .label text,#mermaid-svg-SMk8tjbTAM3dfX47 .node .label text,#mermaid-svg-SMk8tjbTAM3dfX47 .image-shape .label,#mermaid-svg-SMk8tjbTAM3dfX47 .icon-shape .label{text-anchor:middle;}#mermaid-svg-SMk8tjbTAM3dfX47 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SMk8tjbTAM3dfX47 .rough-node .label,#mermaid-svg-SMk8tjbTAM3dfX47 .node .label,#mermaid-svg-SMk8tjbTAM3dfX47 .image-shape .label,#mermaid-svg-SMk8tjbTAM3dfX47 .icon-shape .label{text-align:center;}#mermaid-svg-SMk8tjbTAM3dfX47 .node.clickable{cursor:pointer;}#mermaid-svg-SMk8tjbTAM3dfX47 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SMk8tjbTAM3dfX47 .arrowheadPath{fill:#333333;}#mermaid-svg-SMk8tjbTAM3dfX47 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SMk8tjbTAM3dfX47 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SMk8tjbTAM3dfX47 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SMk8tjbTAM3dfX47 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SMk8tjbTAM3dfX47 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SMk8tjbTAM3dfX47 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster text{fill:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 .cluster span{color:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SMk8tjbTAM3dfX47 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SMk8tjbTAM3dfX47 rect.text{fill:none;stroke-width:0;}#mermaid-svg-SMk8tjbTAM3dfX47 .icon-shape,#mermaid-svg-SMk8tjbTAM3dfX47 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SMk8tjbTAM3dfX47 .icon-shape p,#mermaid-svg-SMk8tjbTAM3dfX47 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SMk8tjbTAM3dfX47 .icon-shape .label rect,#mermaid-svg-SMk8tjbTAM3dfX47 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SMk8tjbTAM3dfX47 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SMk8tjbTAM3dfX47 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SMk8tjbTAM3dfX47 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-SMk8tjbTAM3dfX47 .primary>*{fill:#4FC3F7!important;stroke:#0288D1!important;color:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .primary span{fill:#4FC3F7!important;stroke:#0288D1!important;color:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .primary tspan{fill:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .warning>*{fill:#FFB74D!important;stroke:#F57C00!important;color:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .warning span{fill:#FFB74D!important;stroke:#F57C00!important;color:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .warning tspan{fill:#000!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .error>*{fill:#EF5350!important;stroke:#C62828!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .error span{fill:#EF5350!important;stroke:#C62828!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .error tspan{fill:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .success>*{fill:#66BB6A!important;stroke:#2E7D32!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .success span{fill:#66BB6A!important;stroke:#2E7D32!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .success tspan{fill:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .info>*{fill:#CE93D8!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .info span{fill:#CE93D8!important;stroke:#7B1FA2!important;color:#fff!important;}#mermaid-svg-SMk8tjbTAM3dfX47 .info tspan{fill:#fff!important;} 命中黑名单/正则
规则通过
高风险类别
低风险/正常
人工确认违规
人工确认正常
📝 原始文本输入
🔍 第一级:规则过滤
🚫 直接拦截
🧠 第二级:模型分类
⚠️ 人工复审队列
✅ 放行发布
🚫 拦截+用户通知

这个流水线的精髓在于:每一级只处理上一级无法确定的样本。规则引擎能搞定的,就不需要模型介入;模型能搞定的,就不需要人工复审。这样层层递进,既保证了效率,又保证了安全。

2.3 端侧推理的关键考量

在HarmonyOS端侧做文本分类推理,需要考虑几个关键问题:

  1. 模型量化:将FP32模型量化为INT8,推理速度提升2-4倍,精度损失通常在1%以内
  2. 批处理优化:多条文本合并为一个batch推理,充分利用NPU并行计算能力
  3. 缓存策略:相同文本的分类结果缓存,避免重复推理
  4. 降级方案:当NPU不可用时,自动降级到CPU推理

三、代码实战

3.1 基于规则引擎的文本分类器

这是最基础但也最实用的分类方式,适合处理模式明确的文本:

typescript 复制代码
/**
 * 规则引擎文本分类器
 * 基于正则表达式和关键词匹配的快速文本分类
 */
import { HashMap } from '@kit.ArkTS';

// 分类规则定义
interface ClassificationRule {
  name: string;           // 分类名称
  keywords: string[];     // 关键词列表
  patterns: RegExp[];     // 正则表达式列表
  priority: number;       // 优先级,数值越大优先级越高
  confidence: number;     // 命中后的置信度 0-1
}

// 分类结果
interface ClassificationResult {
  category: string;       // 分类名称
  confidence: number;     // 置信度
  matchedRules: string[]; // 命中的规则
  processingTime: number; // 处理耗时(ms)
}

export class RuleBasedClassifier {
  private rules: ClassificationRule[] = [];
  private cache: HashMap<string, ClassificationResult> = new HashMap();

  constructor() {
    this.initDefaultRules();
  }

  // 初始化默认分类规则
  private initDefaultRules(): void {
    this.rules = [
      {
        name: '广告推广',
        keywords: ['免费领取', '限时优惠', '加微信', '扫码领取', '折扣码', '优惠券'],
        patterns: [
          /加[微V]信[::]?\s*[a-zA-Z0-9_-]{5,}/,
          /(?:点击|戳|扫)[链接二维码]?.*?(?:领取|获取|免费)/,
          /(?:原价|原价¥?)\d+.*?(?:现价|仅需|只要)¥?\d+/,
        ],
        priority: 10,
        confidence: 0.85,
      },
      {
        name: '垃圾信息',
        keywords: ['代开', '办理', '发票', '贷款', '套现', '刷单'],
        patterns: [
          /(?:代开|办理).{0,4}(?:发票|证明|资质)/,
          /(?:无抵押|免审核|秒下款).{0,6}(?:贷款|借款|额度)/,
        ],
        priority: 9,
        confidence: 0.9,
      },
      {
        name: '暴力恐怖',
        keywords: ['杀', '砍', '炸弹', '恐怖袭击'],
        patterns: [
          /(?:我要|想要|准备).{0,4}(?:杀|砍|捅|炸)/,
        ],
        priority: 15,
        confidence: 0.95,
      },
      {
        name: '色情低俗',
        keywords: ['约炮', '裸聊', '色情'],
        patterns: [
          /(?:约|找).{0,3}(?:炮|妹子|美女).{0,3}(?:开房|上门)/,
        ],
        priority: 14,
        confidence: 0.9,
      },
      {
        name: '正常内容',
        keywords: [],
        patterns: [],
        priority: 0,
        confidence: 0.5,
      },
    ];

    // 按优先级降序排列
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  // 添加自定义规则
  addRule(rule: ClassificationRule): void {
    this.rules.push(rule);
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  // 分类单条文本
  classify(text: string): ClassificationResult {
    const startTime = Date.now();

    // 检查缓存
    const cached = this.cache.get(text);
    if (cached !== undefined) {
      return { ...cached, processingTime: Date.now() - startTime };
    }

    const matchedRules: string[] = [];
    let bestCategory = '正常内容';
    let bestConfidence = 0.5;

    for (const rule of this.rules) {
      let isMatched = false;

      // 关键词匹配
      for (const keyword of rule.keywords) {
        if (text.includes(keyword)) {
          isMatched = true;
          matchedRules.push(`关键词:${keyword}`);
          break;
        }
      }

      // 正则匹配
      for (const pattern of rule.patterns) {
        if (pattern.test(text)) {
          isMatched = true;
          matchedRules.push(`正则:${pattern.source}`);
          break;
        }
      }

      // 命中规则且优先级更高,更新结果
      if (isMatched && rule.confidence > bestConfidence) {
        bestCategory = rule.name;
        bestConfidence = rule.confidence;
      }
    }

    const result: ClassificationResult = {
      category: bestCategory,
      confidence: bestConfidence,
      matchedRules: matchedRules,
      processingTime: Date.now() - startTime,
    };

    // 写入缓存
    this.cache.set(text, result);

    return result;
  }

  // 批量分类
  classifyBatch(texts: string[]): ClassificationResult[] {
    return texts.map(text => this.classify(text));
  }

  // 清除缓存
  clearCache(): void {
    this.cache.clear();
  }
}

3.2 多级内容审核流水线

将规则引擎和模型分类组合成完整的审核流水线:

typescript 复制代码
/**
 * 多级内容审核流水线
 * 规则引擎 → 模型分类 → 人工复审,逐级过滤
 */

// 审核级别枚举
enum AuditLevel {
  PASS = 'pass',           // 通过
  REVIEW = 'review',       // 待复审
  REJECT = 'reject',       // 拒绝
}

// 审核结果
interface AuditResult {
  level: AuditLevel;
  category: string;
  confidence: number;
  reason: string;
  pipeline: string;        // 经过的审核阶段
  timestamp: number;
}

// 审核配置
interface AuditConfig {
  enableRuleFilter: boolean;       // 启用规则过滤
  enableModelClassify: boolean;    // 启用模型分类
  autoRejectThreshold: number;     // 自动拒绝阈值
  autoPassThreshold: number;       // 自动通过阈值
  maxBatchSize: number;            // 最大批量大小
}

export class ContentAuditPipeline {
  private classifier: RuleBasedClassifier;
  private config: AuditConfig;
  private reviewQueue: AuditResult[] = [];  // 人工复审队列

  constructor(config?: Partial<AuditConfig>) {
    this.classifier = new RuleBasedClassifier();
    this.config = {
      enableRuleFilter: true,
      enableModelClassify: true,
      autoRejectThreshold: 0.9,
      autoPassThreshold: 0.6,
      maxBatchSize: 50,
      ...config,
    };
  }

  // 执行完整审核流水线
  async audit(text: string): Promise<AuditResult> {
    const timestamp = Date.now();
    let pipeline = '';

    // ===== 第一级:规则引擎快速过滤 =====
    if (this.config.enableRuleFilter) {
      pipeline += '规则过滤→';
      const ruleResult = this.classifier.classify(text);

      // 高风险内容直接拦截
      if (ruleResult.confidence >= this.config.autoRejectThreshold &&
          ruleResult.category !== '正常内容') {
        return {
          level: AuditLevel.REJECT,
          category: ruleResult.category,
          confidence: ruleResult.confidence,
          reason: `规则引擎拦截:${ruleResult.matchedRules.join(', ')}`,
          pipeline: pipeline + '拦截',
          timestamp,
        };
      }

      // 规则引擎通过,进入下一级
      if (ruleResult.category === '正常内容' &&
          ruleResult.confidence >= this.config.autoPassThreshold) {
        // 规则引擎认为正常,但还需要模型确认
        if (!this.config.enableModelClassify) {
          return {
            level: AuditLevel.PASS,
            category: '正常内容',
            confidence: ruleResult.confidence,
            reason: '规则引擎判定为正常内容',
            pipeline: pipeline + '通过',
            timestamp,
          };
        }
      }
    }

    // ===== 第二级:模型智能分类 =====
    if (this.config.enableModelClassify) {
      pipeline += '模型分类→';
      const modelResult = await this.modelClassify(text);

      // 模型判定高风险
      if (modelResult.confidence >= this.config.autoRejectThreshold) {
        return {
          level: AuditLevel.REJECT,
          category: modelResult.category,
          confidence: modelResult.confidence,
          reason: `模型分类拦截:${modelResult.category}`,
          pipeline: pipeline + '拦截',
          timestamp,
        };
      }

      // 模型判定正常
      if (modelResult.confidence <= this.config.autoPassThreshold ||
          modelResult.category === '正常内容') {
        return {
          level: AuditLevel.PASS,
          category: modelResult.category,
          confidence: modelResult.confidence,
          reason: '模型分类判定为正常内容',
          pipeline: pipeline + '通过',
          timestamp,
        };
      }

      // 模型不确定,进入人工复审
      const reviewResult: AuditResult = {
        level: AuditLevel.REVIEW,
        category: modelResult.category,
        confidence: modelResult.confidence,
        reason: `模型置信度不足,需人工确认:${modelResult.category}`,
        pipeline: pipeline + '复审',
        timestamp,
      };
      this.reviewQueue.push(reviewResult);
      return reviewResult;
    }

    // 默认通过
    return {
      level: AuditLevel.PASS,
      category: '正常内容',
      confidence: 0.5,
      reason: '未启用审核能力,默认通过',
      pipeline: '无审核',
      timestamp,
    };
  }

  // 模型分类(模拟端侧NLP推理)
  private async modelClassify(text: string): Promise<{
    category: string;
    confidence: number;
  }> {
    // 实际项目中应调用 HarmonyOS NLP API 或 MindSpore Lite 推理
    // 这里用简化的逻辑模拟模型推理过程
    return new Promise((resolve) => {
      setTimeout(() => {
        // 模拟模型输出
        const categories = ['正常内容', '广告推广', '垃圾信息', '暴力恐怖', '色情低俗'];
        const randomIdx = Math.floor(Math.random() * categories.length);
        const confidence = 0.3 + Math.random() * 0.7;
        resolve({
          category: categories[randomIdx],
          confidence: Math.round(confidence * 100) / 100,
        });
      }, 10); // 模拟推理耗时
    });
  }

  // 批量审核
  async auditBatch(texts: string[]): Promise<AuditResult[]> {
    const results: AuditResult[] = [];
    const batch = texts.slice(0, this.config.maxBatchSize);

    for (const text of batch) {
      const result = await this.audit(text);
      results.push(result);
    }

    return results;
  }

  // 获取人工复审队列
  getReviewQueue(): AuditResult[] {
    return [...this.reviewQueue];
  }

  // 人工复审处理
  handleReview(index: number, approved: boolean): void {
    if (index >= 0 && index < this.reviewQueue.length) {
      const item = this.reviewQueue[index];
      item.level = approved ? AuditLevel.PASS : AuditLevel.REJECT;
      item.reason += approved ? ' [人工确认通过]' : ' [人工确认拒绝]';
      this.reviewQueue.splice(index, 1);
    }
  }

  // 获取审核统计
  getStatistics(results: AuditResult[]): Record<string, number> {
    const stats: Record<string, number> = {
      pass: 0,
      review: 0,
      reject: 0,
    };
    results.forEach(r => {
      stats[r.level] = (stats[r.level] || 0) + 1;
    });
    return stats;
  }
}

3.3 完整的文本分类与审核UI组件

将上述能力封装为可复用的UI组件:

typescript 复制代码
/**
 * 文本分类与内容审核UI组件
 * 提供实时文本输入、分类结果展示、审核状态可视化
 */
import { RuleBasedClassifier, ClassificationResult } from './RuleBasedClassifier';
import { ContentAuditPipeline, AuditResult, AuditLevel } from './ContentAuditPipeline';

@ObservedV2
class TextClassificationViewModel {
  @Trace inputText: string = '';
  @Trace classificationResult: ClassificationResult | null = null;
  @Trace auditResult: AuditResult | null = null;
  @Trace isProcessing: boolean = false;
  @Trace historyList: Array<{ text: string; result: AuditResult }> = [];

  private classifier: RuleBasedClassifier = new RuleBasedClassifier();
  private pipeline: ContentAuditPipeline = new ContentAuditPipeline();

  // 实时分类
  classifyText(): void {
    if (!this.inputText.trim()) {
      this.classificationResult = null;
      return;
    }
    this.classificationResult = this.classifier.classify(this.inputText);
  }

  // 执行审核
  async auditText(): Promise<void> {
    if (!this.inputText.trim()) return;
    this.isProcessing = true;

    try {
      this.auditResult = await this.pipeline.audit(this.inputText);
      this.historyList.unshift({
        text: this.inputText,
        result: this.auditResult,
      });
      // 保留最近20条记录
      if (this.historyList.length > 20) {
        this.historyList = this.historyList.slice(0, 20);
      }
    } finally {
      this.isProcessing = false;
    }
  }

  // 获取审核等级颜色
  getAuditLevelColor(level: AuditLevel): string {
    switch (level) {
      case AuditLevel.PASS:
        return '#66BB6A';
      case AuditLevel.REVIEW:
        return '#FFB74D';
      case AuditLevel.REJECT:
        return '#EF5350';
      default:
        return '#9E9E9E';
    }
  }

  // 获取审核等级文本
  getAuditLevelText(level: AuditLevel): string {
    switch (level) {
      case AuditLevel.PASS:
        return '✅ 通过';
      case AuditLevel.REVIEW:
        return '⚠️ 复审';
      case AuditLevel.REJECT:
        return '🚫 拒绝';
      default:
        return '未知';
    }
  }
}

@Entry
@Component
struct TextClassificationPage {
  private viewModel: TextClassificationViewModel = new TextClassificationViewModel();

  build() {
    Column() {
      // 标题栏
      this.TitleBar()

      // 输入区域
      this.InputArea()

      // 分类结果
      this.ClassificationResultArea()

      // 审核结果
      this.AuditResultArea()

      // 历史记录
      this.HistoryArea()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#1A1A2E')
  }

  // 标题栏
  @Builder
  TitleBar() {
    Row() {
      Text('文本分类与内容审核')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#E0E0E0')
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 16, bottom: 16 })
    .alignItems(VerticalAlign.Center)
  }

  // 输入区域
  @Builder
  InputArea() {
    Column() {
      Text('输入待审核文本')
        .fontSize(14)
        .fontColor('#9E9E9E')
        .margin({ bottom: 8 })

      TextArea({
        placeholder: '请输入需要分类和审核的文本内容...',
      })
        .width('100%')
        .height(120)
        .fontSize(16)
        .fontColor('#E0E0E0')
        .backgroundColor('#16213E')
        .borderRadius(12)
        .padding(12)
        .onChange((value: string) => {
          this.viewModel.inputText = value;
          // 实时分类
          this.viewModel.classifyText();
        })

      // 审核按钮
      Button('执行审核')
        .width('100%')
        .height(48)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .backgroundColor('#4FC3F7')
        .fontColor('#1A1A2E')
        .borderRadius(12)
        .margin({ top: 12 })
        .enabled(!this.viewModel.isProcessing)
        .onClick(() => {
          this.viewModel.auditText();
        })
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 8, bottom: 16 })
  }

  // 分类结果展示
  @Builder
  ClassificationResultArea() {
    if (this.viewModel.classificationResult !== null) {
      Column() {
        Text('分类结果')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#E0E0E0')
          .margin({ bottom: 12 })

        Row() {
          // 分类标签
          Text(this.viewModel.classificationResult!.category)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#4FC3F7')
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor('#16213E')
            .borderRadius(8)

          Blank()

          // 置信度
          Column() {
            Text('置信度')
              .fontSize(12)
              .fontColor('#9E9E9E')
            Text(`${(this.viewModel.classificationResult!.confidence * 100).toFixed(1)}%`)
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor('#66BB6A')
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')

        // 命中规则
        if (this.viewModel.classificationResult!.matchedRules.length > 0) {
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.viewModel.classificationResult!.matchedRules, (rule: string) => {
              Text(rule)
                .fontSize(12)
                .fontColor('#FFB74D')
                .backgroundColor('#2D1B00')
                .borderRadius(6)
                .padding({ left: 8, right: 8, top: 4, bottom: 4 })
                .margin({ right: 6, top: 6 })
            })
          }
          .margin({ top: 8 })
        }

        // 处理耗时
        Text(`处理耗时:${this.viewModel.classificationResult!.processingTime}ms`)
          .fontSize(12)
          .fontColor('#666')
          .margin({ top: 8 })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#0F3460')
      .borderRadius(16)
      .margin({ left: 20, right: 20, top: 8 })
    }
  }

  // 审核结果展示
  @Builder
  AuditResultArea() {
    if (this.viewModel.auditResult !== null) {
      Column() {
        Text('审核结果')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#E0E0E0')
          .margin({ bottom: 12 })

        Row() {
          // 审核等级
          Text(this.viewModel.getAuditLevelText(this.viewModel.auditResult!.level))
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.viewModel.getAuditLevelColor(this.viewModel.auditResult!.level))

          Blank()

          // 类别
          Text(this.viewModel.auditResult!.category)
            .fontSize(16)
            .fontColor('#CE93D8')
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .backgroundColor('#2D0033')
            .borderRadius(8)
        }
        .width('100%')

        // 审核原因
        Text(this.viewModel.auditResult!.reason)
          .fontSize(14)
          .fontColor('#BDBDBD')
          .margin({ top: 8 })

        // 审核流水线
        Text(`审核路径:${this.viewModel.auditResult!.pipeline}`)
          .fontSize(12)
          .fontColor('#666')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#0F3460')
      .borderRadius(16)
      .margin({ left: 20, right: 20, top: 12 })
    }
  }

  // 历史记录
  @Builder
  HistoryArea() {
    if (this.viewModel.historyList.length > 0) {
      Column() {
        Text('审核历史')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#E0E0E0')
          .margin({ bottom: 12 })

        List() {
          ForEach(this.viewModel.historyList, (item: { text: string; result: AuditResult },
            index: number) => {
            ListItem() {
              Row() {
                Column() {
                  Text(item.text.length > 30 ? item.text.substring(0, 30) + '...' : item.text)
                    .fontSize(14)
                    .fontColor('#E0E0E0')
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })

                  Text(item.result.reason)
                    .fontSize(12)
                    .fontColor('#9E9E9E')
                    .margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text(this.viewModel.getAuditLevelText(item.result.level))
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(this.viewModel.getAuditLevelColor(item.result.level))
              }
              .width('100%')
              .padding(12)
              .backgroundColor('#16213E')
              .borderRadius(10)
            }
            .margin({ bottom: 8 })
          })
        }
        .width('100%')
        .layoutWeight(1)
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 16, bottom: 16 })
      .layoutWeight(1)
    }
  }
}

四、踩坑与注意事项

4.1 正则表达式性能陷阱

问题:复杂的正则表达式可能导致"灾难性回溯",在处理长文本时CPU占用飙升甚至ANR。

typescript 复制代码
// ❌ 危险:嵌套量词导致回溯爆炸
const badPattern = /(a+)+b/;

// ✅ 安全:使用原子组或简化模式
const safePattern = /a+b/;

解决方案

  • 对正则表达式设置超时机制
  • 限制输入文本长度(建议单条不超过5000字符)
  • 使用非贪婪量词替代嵌套量词
  • 预编译正则表达式,避免重复编译开销

4.2 规则优先级冲突

问题:当多条规则同时命中时,如果优先级设置不当,可能导致误判。

比如"我要杀毒软件优惠"这条文本,同时命中了"暴力恐怖"(关键词"杀")和"广告推广"(关键词"优惠")。如果暴力恐怖优先级更高,就会误判。

解决方案

  • 引入"上下文窗口"概念,关键词匹配时检查前后2-3个字的语境
  • 对高风险类别增加二次确认逻辑
  • 设置"白名单"机制,对已知的误判模式进行豁免

4.3 缓存一致性问题

问题:规则更新后,缓存中仍然存储旧的分类结果,导致新规则不生效。

解决方案

  • 规则更新时自动清除缓存
  • 为缓存设置TTL(建议5分钟)
  • 使用版本号机制,规则变更时递增版本号

4.4 端侧模型推理的内存管理

问题:在低端设备上,NLP模型推理可能占用大量内存,导致OOM。

解决方案

  • 使用MindSpore Lite的INT8量化模型
  • 限制并发推理数量
  • 推理完成后及时释放模型实例
  • 监控内存使用,超过阈值时降级到规则引擎

五、HarmonyOS 6适配

5.1 API变更

能力 HarmonyOS 5.0 HarmonyOS 6.0
NLP文本分类 @kit.AiKit 实验性API @kit.AiKit 正式API,支持自定义模型
MindSpore Lite API 12基础推理 API 14增强推理,支持动态Shape
文本审核服务 服务端API 端侧+服务端混合模式

5.2 迁移指南

typescript 复制代码
// HarmonyOS 5.0 写法
import { textClassification } from '@kit.AiKit';

const result = await textClassification.classify({
  text: inputText,
  model: 'general',
});

// HarmonyOS 6.0 写法(新增自定义模型支持)
import { nlp } from '@kit.AiKit';

const classifier = nlp.createTextClassifier({
  model: 'custom',  // 支持自定义模型
  modelPath: '/data/models/text_classifier.ms',  // 本地模型路径
  labels: ['广告', '正常', '违规'],  // 自定义标签
});

const result = await classifier.classify(inputText);
classifier.destroy();  // 记得释放资源

5.3 新特性适配

HarmonyOS 6.0新增了以下能力,建议适配:

  • 增量学习:支持在端侧对模型进行微调,根据用户反馈持续优化分类效果
  • 联邦审核:多设备协同审核,保护用户隐私的同时提升审核准确率
  • 实时流式审核:支持对输入过程进行实时审核,无需等待完整文本

六、总结

知识点 核心内容
规则引擎分类 基于关键词+正则的快速分类,适合模式明确的场景
多级审核流水线 规则→模型→人工,逐级过滤,效率与安全兼顾
端侧模型推理 MindSpore Lite + INT8量化,平衡精度与性能
缓存策略 结果缓存+TTL+版本号,避免重复推理
降级方案 NPU→CPU→规则引擎,确保服务可用性
HarmonyOS 6适配 自定义模型、增量学习、联邦审核、流式审核

核心思想:文本分类与内容审核不是"一步到位"的技术,而是"层层递进"的工程体系。规则引擎做第一道防线,模型做深度理解,人工做最终兜底------三者结合,才能在安全与体验之间找到最佳平衡点。

实践建议

  1. 先用规则引擎覆盖80%的明确场景,再用模型处理剩余20%的模糊场景
  2. 审核阈值不要一刀切,根据业务场景动态调整
  3. 持续收集误判样本,定期优化规则和模型
  4. 端侧审核+服务端审核双保险,端侧做实时拦截,服务端做深度审核