FIT2CLOUD
内容安全 的图标

内容安全

82
15

在用户注册、资料修改以及评论和回复写入前检测文字与图片,拦截违规内容并记录检测结果。

当前应用开放下载,但部分功能需要购买 Halo 专业版/商城版获得授权

内容安全

内容安全是凌霞软件为 Halo 提供的提交前内容检测插件。它会在注册、公开资料、评论与回复写入前调用内容检测服务或本地词库,拦截不合规内容,并保留可查询、可导出的检测记录。

应用市场 · 问题反馈

preview-1

功能特性

  • 按场景检测:内置用户注册、资料修改、评论与回复;其他 Halo 插件可以贡献业务场景,新场景默认关闭
  • 多服务商组合:免费版支持本地词库与 AI 模型;阿里云、腾讯云及多服务编排需要激活许可证,共享配置、额度和记录
  • 本地词库:内置词库、输入框自定义敏感词和白名单免费;上传多个 .txt 词库文件需要激活许可证,均在服务器内匹配
  • 可选执行方式:免费版固定使用全部通过;通过即放行需要激活许可证
  • 失败不放行:服务不可用、超时、无效输出或记录写入失败都会阻止提交,检测故障与内容违规分别记录
  • 调用保护:按用户、来源 IP 和注册来源限流,并支持全站并发及服务商每日调用额度
  • 完整记录:保存检测结果、策略快照、资料变更和频率拦截事件,免费版支持筛选与 CSV / JSON 导出;合规报表需要激活许可证
  • 可配置告警:免费版支持 Halo 通知与默认规则启停;Webhook、Bark、钉钉、飞书、企业微信及自定义阈值需要激活许可证
  • 覆盖真实写入口:注册、OAuth 首次建号、Console 建号、UC / Console 资料与头像、评论与回复及正文修改、通用资源 POST / PUT / JSON Patch

免费版与许可证

插件本体可免费安装。免费版覆盖本地词库、AI 模型和第三方插件贡献的检测场景;云服务商、多服务编排、外部词库上传、合规报表和完整告警能力需要激活许可证。

能力 免费版 需要激活许可证
第三方插件场景 不限数量 不限数量
本地词库 内置词库、输入框自定义敏感词、白名单 同免费版
AI 模型 支持 支持
阿里云 / 腾讯云 不支持新增 支持
每类内容的检测服务数 单个场景、单个内容类型最多 1 个 多服务编排
执行方式 全部通过 全部通过、通过即放行
.txt 词库上传 不支持 支持
检测记录、资料变更、安全事件及数据导出 支持 支持
记录留存 默认 30 / 180 / 180 天,不能新增长于该上限的期限 最长 365 天
合规报表 不支持 支持
告警渠道 Halo 通知 Halo、Webhook、Bark、钉钉、飞书、企业微信
告警规则 默认规则,可启停 默认规则及自定义阈值

授权在低频管理入口校验,不进入每次内容检测热路径。许可证失效后,已经保存的需要许可证的配置仍会继续运行,已上传的词库和已配置的云服务、告警渠道不会被自动删除;但新增或扩展需要许可证的能力会被拒绝,完成续期后恢复。可在 Halo 控制台的「应用市场 → 许可证管理」查看授权状态并激活许可证。

使用要求

  • Halo 2.27.0 或更高版本
  • 已安装并启用应用市场集成插件 1.x(app-store-integration)
  • 超级管理员权限;Console 与管理 API 仅对超级管理员开放
  • 至少配置一个可用服务:本地词库、阿里云或腾讯云的 Halo Secret,或安装 AI Foundation 1.x 并配置文本及视觉模型
  • 生产环境建议使用 HTTPS;插件按单实例设计,多节点部署需在网关补充共享限流

快速开始

  1. 在 Halo 控制台安装并启用「内容安全」插件。
  2. 打开「内容安全 → 保护配置 → 检测服务」,配置本地词库、AI 模型,或激活许可证后配置阿里云、腾讯云。插件只保存 Secret 引用,不回显密钥。
  3. 保存后分别测试文字和图片连接。本地词库测试不产生费用;云服务测试使用固定样本,计入每日额度,可能产生费用。
  4. 在「检测场景」开启需要检测的场景,选择文本与图片服务商,设置执行方式并按需调整参数。
  5. 按需配置提交频率、单次输入限制、全站调用额度与记录留存,然后启用保护。

关闭场景或不选择任何服务商时,业务按原样继续:不读取图片、不调用服务商、不生成检测记录。没有取得明确通过结果时阻止提交,故障不算用户违规。总体检测期限为 15 秒,不自动重试、截断、拆分或降级放行。检测通过后,Halo 自身仍可能因权限、校验或版本冲突拒绝保存;评论原有的人工审核设置继续生效。

检测场景

内置场景包括用户注册、资料修改、评论与回复。第三方插件贡献的场景也会显示在「检测场景」中,默认关闭,必须由管理员明确启用。

选择多家服务时可拖动调整顺序,并设置执行方式:

执行方式 行为
全部通过才放行(默认) 同时调用列表中的全部服务;任何一家拦截都会拦截,全部通过才放行
通过即放行 按列表顺序调用;拿到通过或拦截结果就停止,只有检测失败时才切换到下一家

图片检测仅支持静态 JPEG / PNG,单图不超过 2 MiB、2500 万像素,每次最多 10 图;GIF、SVG、APNG、外链和其他不支持的媒体会在本地拒绝。

服务商

服务商 配置 说明
本地词库 内置词库、自定义敏感词、白名单、外部词库 文本检测,在服务器内完成匹配,不产生调用费用
阿里云 Secret、地域、文本 / 图片 Service 内容安全增强版 TextModeration / ImageModeration,单字段最多 600 个 UTF-16 单元
腾讯云 Secret、地域、文本 / 图片 BizType TMS / IMS 文本及图片审核,单字段最多 10000 个 UTF-16 单元
AI 模型 AI Foundation 的文本模型、视觉模型 输出必须符合结构化结果,不调用工具;补充要求不能替代基础安全规则

本地词库内置约 6 万个通用敏感词,支持自定义敏感词、白名单和多个 UTF-8 / GB18030 / GBK .txt 词库文件,上传后立即参与匹配。每行一个词,以 # 开头的行会被忽略;单个文件不超过 2 MB,最多 20 个文件。仅支持文字检测。

Secret 由 Halo 管理。保存启用策略时会检查凭据是否存在且完整,运行时重新读取;读取失败会阻止提交。使用阿里云图片检测时,待检字节会通过官方临时上传凭据发送至阿里云,数据处理及留存遵循服务商条款。

记录与告警

检测记录、资料变更和限流事件保存在插件数据目录的 content-safety/content-safety.sqlite,不使用 Halo Extension 保存。检测记录会保存策略快照和脱敏文本摘要,不保存图片、完整服务商响应、密钥或密码。默认通过 / 故障记录保留 30 天,资料差异 180 天,违规记录和安全事件 180 天。

三个记录页签均支持按当前筛选导出 CSV / JSON,单次最多 50,000 行;合规报表按 Asia/Shanghai 日历日统计,最多 366 天。导出和报表不包含提交原文、图片、完整服务商响应或密钥。

告警只用于通知,不参与检测决策,发送失败不会改变提交结果。支持 Webhook、Halo 通知、Bark、钉钉、飞书和企业微信,每个渠道可独立选择事件。Webhook 可选 Halo Secret 签名,请求失败最多重试 2 次。

适用范围

插件只拦截经过 Halo HTTP 写入口的提交。其他插件在 JVM 内直接保存内容时,必须在保存前调用公开 Java API。对象存储绕过 Halo 覆盖文件、主题自动生成头像、历史内容巡检不在此范围;插件也不提供实名验证、人工复核、处罚或申诉流程。

界面预览

preview-2

preview-3

preview-4

preview-5

preview-6

开发

内容安全提供公开 Java API,其他 Halo 插件可以通过它贡献检测场景,并在保存业务数据前执行检测。插件是可选集成:未安装或未启用内容安全时,消费方插件应继续正常工作。

展开查看开发与接入文档

添加 API 依赖

API 以 run.halo.contentsafety:api 发布。消费方只把它作为编译期依赖,不要通过 implementation 打入自己的插件;请将 <version> 替换为实际发布版本。

dependencies {
    compileOnly 'run.halo.contentsafety:api:<version>'
    testCompileOnly 'run.halo.contentsafety:api:<version>'
    testRuntimeOnly 'run.halo.contentsafety:api:<version>'
}

声明可选插件依赖

在 plugin.yaml 中把内容安全声明为可选依赖(键名末尾加 ?):

spec:
    requires: ">=2.27.0"
    pluginDependencies:
        content-safety?: ">=1.0.0 & <2.0.0"

可选依赖只解决类可见性和启动顺序。所有直接引用 ContentSafetyService、ContentSafetySceneContributor 等 API 类型的 Bean 都必须通过条件注册的 @Configuration 创建,不能在普通 @Component 上只加条件:

@Configuration
@ConditionalOnClass(name = "run.halo.contentsafety.api.ContentSafetyService")
class ContentSafetyConfiguration {
    // 在这里通过 @Bean 创建引用内容安全类型的适配器。
}

贡献场景

实现 ContentSafetySceneContributor,返回不可变的场景定义:

public class MyScenes implements ContentSafetySceneContributor {
    @Override
    public List<SceneDefinition> scenes() {
        return List.of(new SceneDefinition(
            "my-plugin:submission",
            "my-plugin",
            "业务提交",
            "在保存业务数据前检查标题和正文。",
            Set.of(ContentType.TEXT, ContentType.IMAGE)));
    }
}

通过上面的条件配置注册,并声明 ExtensionDefinition:

apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
    name: my-plugin-content-safety-scenes
spec:
    className: example.MyScenes
    extensionPointName: content-safety-scenes
    displayName: 业务内容检查场景
  • 场景 ID 使用 插件 ID:名称 形式,前缀必须与 pluginId 一致。
  • displayName 和 description 面向管理员,使用中文说明检测发生在哪里。
  • contentTypes 至少声明一项,只声明实际会提交的内容类型。
  • 重复 ID 会被忽略并记录警告,内置场景优先。停用插件会移除动态场景,但保留已保存配置。
  • 新场景默认关闭,必须由管理员在「检测场景」中明确启用。

检测后再保存

把 API 调用放在条件注册的适配组件里,业务入口只注入消费方自己的接口。下面是一个完整的文本检测适配器:

public interface ContentCheck {
    Mono<Void> inspect(String scene, String actor, String resourceId, String correlationId, String text);
}

class ContentSafetyCheck implements ContentCheck {
    private final ExtensionGetter extensionGetter;

    ContentSafetyCheck(ExtensionGetter extensionGetter) {
        this.extensionGetter = extensionGetter;
    }

    @Override
    public Mono<Void> inspect(
            String scene, String actor, String resourceId, String correlationId, String text) {
        return extensionGetter.getEnabledExtension(ContentSafetyService.class)
            .flatMap(service -> service.inspect(new InspectionRequest(
                scene, actor, resourceId, correlationId,
                List.of(new InspectionInput("text", text, null, null)))))
            .doOnNext(InspectionReport::requireAllowed)
            .onErrorMap(
                InspectionRejectedException.class,
                error -> new ResponseStatusException(
                    error.report().outcome() == InspectionReport.Outcome.BLOCK
                        ? HttpStatus.UNPROCESSABLE_ENTITY
                        : HttpStatus.SERVICE_UNAVAILABLE,
                    "提交未通过内容安全检查",
                    error))
            .then();
    }
}

业务入口在保存前执行检测;组件不存在时直接保存:

Mono<Void> check = checkProvider.getIfAvailable() == null
    ? Mono.empty()
    : checkProvider.getObject().inspect(scene, actor, resourceId, correlationId, text);
return check.then(Mono.defer(this::saveBusinessData));
  • 未安装或未启用内容安全时,适配组件不存在,业务照常保存。这不表示内容已通过检测。
  • 适配组件存在但 getEnabledExtension 返回空时,应按“站点未配置检测”处理;业务不能接受这种降级时,应在适配组件内显式返回错误。
  • 场景未启用时,inspect 返回 SKIPPED,业务照常保存;同样不能当作已检测。
  • 场景启用后,PASS 放行,BLOCK / ERROR 阻止保存。
  • actor 必须来自可信认证上下文;resourceId 和 correlationId 只用于记录,不是幂等键。
  • 检测必须发生在保存最终内容之前,修改内容后需要重新检测。检测通过不代表业务保存成功。

结果与错误处理

结果 含义 是否允许继续
PASS 按场景配置的执行方式得到了明确通过结果 是
SKIPPED 场景关闭,或本次没有待检查内容 是
BLOCK 内容违规 否
ERROR 服务、额度或容量故障;内容未取得明确结果 否

report.requireAllowed() 在 PASS、SKIPPED 之外抛出携带报告的 InspectionRejectedException。调用方应把 BLOCK 映射为 422,把 ERROR 映射为 503;频率、每日额度或容量限制可映射为 429,并在 retryAfterSeconds 非空时附 Retry-After。不要把异常 message 或原因码直接作为 UI 文案。

内容安全已启用时,未知场景、无效请求和 SQLite 故障仍可能以 Mono error 返回。调用方应传播失败,不能 onErrorResume 为通过。API 不提供“跳过检测”参数,也不执行业务写入;业务插件仍须对自身入口做鉴权、请求体限额和必要的 IP 频率控制。

输入与调用限制

  • 一个请求最多 32 项输入,id 在整个请求内唯一。
  • 每项输入只能提供 text、image 字节、attachmentName 中的一项,不能传 URL 让检测服务下载。
  • 文本硬上限为 10000 个 UTF-16 单元;启用阿里云时,单个字段超过 600 个 UTF-16 单元会被拒绝。
  • 图片仅支持静态 PNG / JPEG,单图不超过 2 MiB、2500 万像素;每次最多 10 图,可按场景收紧。
  • 超过启用的限制时整批拒绝,付费调用前校验,不截断、不拆分。
  • 检测受场景频率、全站并发、服务商每日额度和 15 秒总体期限控制。

接入验证

安装内容安全插件和消费方插件后,至少验证:

  1. 未安装或未启用内容安全时,消费方插件正常启动,业务数据正常写入,且不产生检测记录。
  2. 安装并启用内容安全后,消费方的场景出现在「检测场景」中,且默认关闭。
  3. 场景启用后,违规内容或检测故障被拒绝,且不写入业务数据。

许可证

本插件通过 Halo 应用市场分发,适用 Halo 应用市场服务条款。


讨论(0)