01MVP 标识01MVP

tencent-cloud 腾讯云服务

腾讯云内容安全审核服务集成,支持文本和图片审核

概述

@mono/tencent-cloud 是腾讯云服务集成包,提供文本和图片内容安全审核功能。基于腾讯云官方 SDK 封装,提供简洁易用的 API 和完整的 TypeScript 类型支持。

为什么需要内容审核?

在用户生成内容(UGC)的应用中,内容审核是必不可少的功能:

  • 合规要求: 符合国家法律法规,避免违规内容
  • 用户体验: 过滤垃圾信息、广告、恶意内容
  • 品牌保护: 防止不良内容影响品牌形象
  • 自动化: 减少人工审核成本,提高效率

功能特性

文本内容审核

  • ✅ 违规内容检测(政治、色情、暴力、广告等)
  • ✅ 批量审核支持
  • ✅ 自定义业务类型
  • ✅ 违规关键词提取
  • ✅ 审核建议(通过/拦截/人工审核)

图片内容审核

  • ✅ 多种检测方式(文件路径、URL)
  • ✅ 违规图片识别
  • ✅ OCR 文字识别
  • ✅ 物体检测
  • ✅ 人脸识别
  • ✅ 自定义库匹配

快速开始

配置环境变量

.env.local 中配置腾讯云密钥:

TENCENT_CLOUD_SECRET_ID=your-secret-id
TENCENT_CLOUD_SECRET_KEY=your-secret-key
TENCENT_CLOUD_REGION=ap-shanghai  # 可选,默认 ap-shanghai

获取腾讯云密钥

  1. 登录 腾讯云控制台
  2. 访问 API 密钥管理
  3. 创建或查看 SecretId 和 SecretKey
  4. 确保账号已开通 内容安全服务

文本审核示例

import { createTencentTextModerationClientFromEnv } from "@mono/tencent-cloud";

// 从环境变量创建客户端
const client = createTencentTextModerationClientFromEnv();

// 审核文本内容
const result = await client.moderateText("用户评论内容");

if (result.suggestion === "Pass") {
  console.log("✅ 内容安全");
} else if (result.suggestion === "Block") {
  console.log("❌ 内容违规:", result.label);
  console.log("违规关键词:", result.keywords);
} else {
  console.log("⚠️ 需要人工审核");
}

图片审核示例

import { createTencentImageModerationClientFromEnv } from "@mono/tencent-cloud";

const client = createTencentImageModerationClientFromEnv();

// 方式 1: 通过 URL 审核
const result = await client.moderateImage({
  fileUrl: "https://example.com/image.jpg",
});

// 方式 2: 通过文件路径审核
const result = await client.moderateImage({
  filePath: "/path/to/image.jpg",
});

if (result.suggestion === "Pass") {
  console.log("✅ 图片安全");
} else {
  console.log("❌ 图片违规:", result.label);
  console.log("详细结果:", result.labelResults);
}

实战场景

场景 1: 用户评论审核

在用户发表评论时自动审核内容:

import { createTencentTextModerationClientFromEnv } from "@mono/tencent-cloud";
import { prisma } from "@/lib/prisma";

const moderationClient = createTencentTextModerationClientFromEnv();

export async function createComment(userId: string, content: string) {
  // 1. 审核内容
  const moderation = await moderationClient.moderateText(content);

  // 2. 根据审核结果决定状态
  if (moderation.suggestion === "Block") {
    throw new Error("评论内容违规,无法发布");
  }

  // 3. 创建评论
  const comment = await prisma.comment.create({
    data: {
      userId,
      content,
      // 可疑内容标记为待审核
      status: moderation.suggestion === "Review" ? "pending" : "approved",
      // 保存审核信息用于分析
      moderationLabel: moderation.label,
      moderationScore: moderation.score,
    },
  });

  return comment;
}

场景 2: 用户头像审核

上传头像时检查图片内容:

import { createTencentImageModerationClientFromEnv } from "@mono/tencent-cloud";

const moderationClient = createTencentImageModerationClientFromEnv();

export async function validateAvatar(imageUrl: string) {
  const result = await moderationClient.moderateImage({
    fileUrl: imageUrl,
    bizType: "avatar", // 自定义业务类型
  });

  // 检查是否违规
  if (result.suggestion === "Block") {
    return {
      valid: false,
      reason: `图片包含违规内容: ${result.label}`,
    };
  }

  // 检查是否包含文字(头像不应包含文字)
  if (result.ocrResults.length > 0) {
    const hasText = result.ocrResults.some(
      (ocr) => ocr.Text && ocr.Text.length > 5
    );
    if (hasText) {
      return {
        valid: false,
        reason: "头像不应包含过多文字",
      };
    }
  }

  return { valid: true };
}

场景 3: 批量内容审核

定期审核历史内容:

import { createTencentTextModerationClientFromEnv } from "@mono/tencent-cloud";
import { prisma } from "@/lib/prisma";

const moderationClient = createTencentTextModerationClientFromEnv();

export async function auditPendingComments() {
  // 1. 获取待审核评论
  const comments = await prisma.comment.findMany({
    where: { status: "pending" },
    take: 100,
  });

  // 2. 批量审核
  const contents = comments.map((c) => c.content);
  const results = await moderationClient.moderateTexts(contents);

  // 3. 更新状态
  const updates = comments.map((comment, index) => {
    const result = results[index];
    return prisma.comment.update({
      where: { id: comment.id },
      data: {
        status: result.suggestion === "Pass" ? "approved" : "rejected",
        moderationLabel: result.label,
        moderationScore: result.score,
      },
    });
  });

  await Promise.all(updates);

  return {
    total: comments.length,
    approved: results.filter((r) => r.suggestion === "Pass").length,
    rejected: results.filter((r) => r.suggestion === "Block").length,
  };
}

场景 4: API 路由集成

创建审核 API 端点:

// app/api/moderation/text/route.ts
import { createTencentTextModerationClientFromEnv } from "@mono/tencent-cloud";
import { NextResponse } from "next/server";

const client = createTencentTextModerationClientFromEnv();

export async function POST(request: Request) {
  try {
    const { content } = await request.json();

    if (!content || typeof content !== "string") {
      return NextResponse.json(
        { error: "Invalid content" },
        { status: 400 }
      );
    }

    const result = await client.moderateText(content);

    return NextResponse.json({
      suggestion: result.suggestion,
      label: result.label,
      score: result.score,
      keywords: result.keywords,
    });
  } catch (error) {
    console.error("Moderation error:", error);
    return NextResponse.json(
      { error: "Moderation failed" },
      { status: 500 }
    );
  }
}

场景 5: 实时审核 Hook

创建 React Hook 用于客户端实时审核:

// hooks/use-content-moderation.ts
"use client";

import { useState } from "react";

export function useContentModeration() {
  const [isChecking, setIsChecking] = useState(false);

  const checkContent = async (content: string) => {
    setIsChecking(true);
    try {
      const response = await fetch("/api/moderation/text", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ content }),
      });

      const result = await response.json();
      return result;
    } finally {
      setIsChecking(false);
    }
  };

  return { checkContent, isChecking };
}

// 使用示例
function CommentForm() {
  const { checkContent, isChecking } = useContentModeration();

  const handleSubmit = async (content: string) => {
    const result = await checkContent(content);

    if (result.suggestion === "Block") {
      alert(`内容违规: ${result.label}`);
      return;
    }

    // 提交评论
    await submitComment(content);
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(e.currentTarget.content.value);
    }}>
      <textarea name="content" />
      <button disabled={isChecking}>
        {isChecking ? "检查中..." : "提交"}
      </button>
    </form>
  );
}

场景 6: 降级策略

审核服务不可用时的降级处理:

import { createTencentTextModerationClientFromEnv } from "@mono/tencent-cloud";

const client = createTencentTextModerationClientFromEnv();

// 本地关键词黑名单(降级方案)
const bannedKeywords = ["违禁词1", "违禁词2"];

export async function moderateWithFallback(content: string) {
  try {
    // 尝试使用腾讯云审核
    const result = await client.moderateText(content);
    return {
      suggestion: result.suggestion,
      source: "tencent-cloud",
    };
  } catch (error) {
    console.error("审核服务不可用,使用本地规则:", error);

    // 降级到本地关键词过滤
    const hasViolation = bannedKeywords.some((keyword) =>
      content.includes(keyword)
    );

    return {
      suggestion: hasViolation ? "Block" : "Pass",
      source: "local-fallback",
    };
  }
}

API 参考

文本审核

moderateText(content, bizType?)

审核单个文本内容。

参数:

  • content (string): 要检测的文本内容
  • bizType (string, 可选): 业务类型

返回:

{
  suggestion: "Pass" | "Block" | "Review";
  label: string;        // 违规标签
  score: number;        // 违规分数 (0-100)
  keywords: string[];   // 违规关键词
  subLabel: string;     // 子标签
}

moderateTexts(contents, bizType?)

批量审核多个文本。

参数:

  • contents (string[]): 文本数组
  • bizType (string, 可选): 业务类型

返回: Promise<TextModerationResult[]>

isTextSafe(content, bizType?)

判断文本是否安全。

返回: Promise<boolean>

图片审核

moderateImage(options)

审核图片内容。

参数:

{
  filePath?: string;      // 本地文件路径
  fileUrl?: string;       // 图片 URL
  dataId?: string;        // 数据 ID
  bizType?: string;       // 业务类型
  type?: string;          // 图片类型
  interval?: number;      // 截帧间隔(视频)
  maxFrames?: number;     // 最大截帧数(视频)
}

返回:

{
  suggestion: "Pass" | "Block" | "Review";
  label: string;
  subLabel: string;
  score: number;
  labelResults: Array<{...}>;      // 标签检测结果
  objectResults: Array<{...}>;     // 物体检测结果
  ocrResults: Array<{...}>;        // OCR 识别结果
  libResults: Array<{...}>;        // 自定义库结果
  recognitionResults: Array<{...}>; // 人脸识别结果
}

isImageSafe(options)

判断图片是否安全。

返回: Promise<boolean>

审核结果说明

审核建议 (suggestion)

  • Pass: 内容安全,可以放行
  • Block: 内容违规,建议拦截
  • Review: 内容可疑,建议人工审核

违规标签 (label)

常见标签包括:

  • Normal: 正常内容
  • Porn: 色情内容
  • Abuse: 谩骂内容
  • Ad: 广告内容
  • Illegal: 违法内容
  • Spam: 垃圾信息
  • Political: 政治敏感
  • Terrorism: 暴恐内容

违规分数 (score)

  • 范围: 0-100
  • 分数越高,违规可能性越大
  • 通常 > 80 建议拦截

最佳实践

1. 异步审核

对于非关键路径,使用异步审核:

// 先保存,后审核
const comment = await prisma.comment.create({
  data: { content, status: "pending" },
});

// 异步审核
moderateComment(comment.id).catch(console.error);

2. 缓存审核结果

对于相同内容,缓存审核结果:

import { createHash } from "crypto";

const cache = new Map<string, TextModerationResult>();

async function moderateWithCache(content: string) {
  const hash = createHash("md5").update(content).digest("hex");

  if (cache.has(hash)) {
    return cache.get(hash)!;
  }

  const result = await client.moderateText(content);
  cache.set(hash, result);

  return result;
}

3. 记录审核日志

记录审核结果用于分析:

await prisma.moderationLog.create({
  data: {
    userId,
    content,
    suggestion: result.suggestion,
    label: result.label,
    score: result.score,
    keywords: result.keywords,
  },
});

4. 分级处理

根据违规程度采取不同措施:

if (result.score > 90) {
  // 严重违规:直接拒绝
  throw new Error("内容严重违规");
} else if (result.score > 70) {
  // 中度违规:标记待审核
  status = "pending";
} else if (result.score > 50) {
  // 轻度违规:发出警告
  warnings.push("内容可能不当");
}

5. 选择合适的地域

根据业务所在地选择最近的地域:

// 华南地区
const client = createTencentTextModerationClientFromEnv("ap-guangzhou");

// 华东地区
const client = createTencentTextModerationClientFromEnv("ap-shanghai");

// 华北地区
const client = createTencentTextModerationClientFromEnv("ap-beijing");

支持的地域

地域Region说明
华东(上海)ap-shanghai默认地域
华南(广州)ap-guangzhou华南地区
华北(北京)ap-beijing华北地区
西南(成都)ap-chengdu西南地区
西南(重庆)ap-chongqing西南地区

错误处理

认证错误

try {
  const client = createTencentTextModerationClientFromEnv();
} catch (error) {
  // Error: "TENCENT_CLOUD_SECRET_ID and TENCENT_CLOUD_SECRET_KEY environment variables are required"
  console.error("配置错误:", error);
}

审核失败

try {
  const result = await client.moderateText(content);
} catch (error) {
  // Error: "Text moderation failed: [具体错误]"
  console.error("审核失败:", error);
  // 实施降级策略
}

参数错误

try {
  await client.moderateImage({});
} catch (error) {
  // Error: "Either filePath or fileUrl must be provided"
  console.error("参数错误:", error);
}

性能优化

批量处理

// ✅ 推荐: 批量审核
const results = await client.moderateTexts(contents);

// ❌ 避免: 逐个审核
for (const content of contents) {
  await client.moderateText(content);
}

并发控制

import pLimit from "p-limit";

const limit = pLimit(5); // 最多 5 个并发

const results = await Promise.all(
  contents.map((content) =>
    limit(() => client.moderateText(content))
  )
);

故障排查

问题 1: 认证失败

症状: AuthFailure 错误

解决方案:

  1. 检查 SecretId 和 SecretKey 是否正确
  2. 确认账号已开通内容安全服务
  3. 检查 API 密钥权限

问题 2: 地域不支持

症状: UnsupportedRegion 错误

解决方案:

  1. 检查地域代码是否正确
  2. 使用默认地域 ap-shanghai
  3. 查看 支持的地域列表

问题 3: 请求频率限制

症状: RequestLimitExceeded 错误

解决方案:

  1. 实现请求限流
  2. 使用批量审核接口
  3. 联系腾讯云提升配额

问题 4: 图片审核失败

症状: 图片审核返回错误

解决方案:

  1. 检查图片格式(支持 JPG、PNG、BMP、GIF、WEBP)
  2. 确认图片大小不超过 5MB
  3. 检查图片 URL 是否可访问
  4. 确认文件路径是否正确

相关资源

相关包

  • @mono/content-moderation - 内容审核抽象层
  • tencentcloud-sdk-nodejs - 腾讯云官方 SDK