外观
antispam-cms — 内容审核运营中台(审核池汇聚 + 人工审核工作台 + 运营配置)
分层:审核业务运营层(机审业务模块与人工审核员/客户之间的中台)| 部署单元:5 个(另 2 个有启动类/构建脚本但不构建)| 数据库:TiDB
antispam(master/mirror双源)| base image:private-registry.yidun.internal/yidun/base-image:jdk17(tagmigu-231016)
一、模块定位与架构
它把各机审业务模块(文本/图片/音频/视频/直播/文档等)产生的待确认内容汇聚成审核池,让审核员在控制台按模板、标签、策略完成人工判定并把结论回写业务侧;同时承载账号权限、产品/套餐配置、质检、统计监控等运营能力,对外用 Dubbo + HTTP 契约向其他业务模块和私有化客户暴露查询与配置。
改坏它的典型后果:审核台拉不到数据、判定提交失败、各业务模块依赖的标签/模板/客户/服务配置查询断裂,或客户因签名鉴权改动被挡在门外。账号权限与签名鉴权相关改动属高敏。
架构分层
- 接入层:
console/api—— 审核工作台后端,承载/api/**facade/http-api—— 开放接口 / 回调(骨架,端口 8918)facade/dubbo-provider—— 对各业务模块暴露的 Dubbo 门面console/web—— 前端资源目录(另仓antispam-cms-web是其上游/同源工程)
- 消息管道层:
dispatcher—— 机审消息 → 审核池 ES 的唯一写入方cdc—— CDC / 统计消息消费,并向dispatcher再生产审核消息
- 任务层:
scheduler—— ElasticJob 集合体(质检、通知、服务状态、统计采集) - 业务 / 数据层:
service/business(业务编排 + Kafka + Redis 封装)、service/base(DAO/Manager/动态数据源DynamicDataSource) - 模型层:
domain(模型与请求/响应 DTO)、common(枚举常量)、client(对外 SDK)
关键数据流
- 机审业务模块(
antispam-business-*)推 KafkaAntispam_Audit_Msg dispatcher的MediaCensorDispatchConsumer→CensorDispatcher(filter → executor)→EsIndexExecutor写 ES 审核池console/api各审核 Controller 查询审核池 → 审核员提交判定cdc消费Antispam_Cdc_*及文本/图片等消息生成监控统计;TextAuditConsumer/ImageAuditConsumer处理后再投Antispam_Audit_Msg交给dispatcherscheduler的 ElasticJob 做质检抽检、超时通知、服务开通关闭、每日统计- 判定结果经 Dubbo/HTTP 回调或由业务模块自取回写;
antispam-bill的套餐变更事件(Antispam_Bill_Package_Change)驱动服务开通/关闭
权限与审核池约束
CmsAuthority枚举(菜单/按钮权限,含分组与区域/版本约束)是权限唯一真源;账号的authorities字符串与套餐表service_edition.Authorities都引用其枚举名,新增权限必须同步订正CmsAuthority→PACKAGE_BASE_AUTH→service_edition.Authorities→ 平台/前端/反垃圾后台四处配置。- 审核池 ES 数据只允许经
dispatcher的消息管道写入,前端/后端不得绕过管道直写,否则查询索引与统计口径不一致。
二、可部署服务清单
| 部署单元目录 | artifactId | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml |
|---|---|---|---|---|---|---|
console/api | antispam-cms-console-api | private-registry.yidun.internal/yidun/antispam-cms-console-api:migu-231016 | 8908 | Spring Boot Web(审核台后端) | 有 | 否(本仓 kubernetes.yml 未含) |
facade/dubbo-provider | antispam-cms-facade-dubbo-provider | private-registry.yidun.internal/yidun/antispam-cms-facade-dubbo-provider:migu-231016 | Dubbo register=false(无 Web) | Dubbo Provider | 有 | 是(Deployment 名 antispam-cms-facade-dubbo-provider,replicas=2) |
dispatcher | antispam-cms-dispatcher | private-registry.yidun.internal/yidun/antispam-cms-dispatcher:migu-231016 | 无 Web | Kafka Consumer(审核池分发) | 有 | 否 |
cdc | antispam-cms-cdc | private-registry.yidun.internal/yidun/antispam-cms-cdc:migu-231016 | 无 Web | Kafka Consumer + Producer | 有 | 否 |
scheduler | antispam-cms-scheduler | private-registry.yidun.internal/yidun/antispam-cms-scheduler:migu-231016 | 无 Web | ElasticJob(40+ 任务) | 有 | 否 |
facade/http-api | antispam-cms-facade-http-api | 无(不构建) | 8918 | Spring Boot Web(开放接口/回调) | 无 | 否 |
仓库根 console/web(前端工程) | name=antispam-cms-web(package.json) | 无(不构建) | — | Vue 2.7 + Vite 4 SPA | 无 | 否(有 console/web/kubernetes.yml,与父级内容同源) |
构建脚本与产物部署方式
buildAll.sh:GLOBAL_ENV=private→git pull→mvn clean install -Dmaven.test.skip=true -Pprivate→ 遍历find . -name "build.sh"以-c false -p false -e private逐个构建(因此只构建上表前 5 个)- 每个
build.sh套路:MODEL_NAME=console/api|facade/dubbo-provider|dispatcher|cdc|scheduler、BUILD_ENV=private、WITH_PULL=true、WITH_COMPILE=true;getopts支持-e/-r/-t/-c/-p覆写;cd ../..→ 可选git pull→ 可选mvn clean install --pl ${MODEL_NAME} -am -T 1C -U -Dmaven.test.skip=true -P ${BUILD_ENV}→cd ${MODEL_NAME}→sudo docker build(带GIT_COMMIT/BUILD_TIME/GIT_BRANCHbuild-arg)→sudo docker push→podupdate.sh - 镜像内落地:
COPY ./target/${APP_NAME}.jar /app/,ENTRYPOINT ["bash","docker-entrypoint.sh"],/app/version.txt记录三个 build-arg kubernetes.yml(仓库根与console/web/)内容是antispam-business-image-*的复制粘贴残留,并不描述本模块的 console/api / dispatcher / cdc / schedulerconsole/web只有buildAll.sh/deploy.sh/location.conf/kubernetes.yml/package.json,没有build.sh,其buildAll.sh遍历find . -name build.sh实际只在父目录层面跑(前端真正构建依赖另仓antispam-cms-web)
三、同模块启动顺序
facade/dubbo-provider—— 下游消费方(各antispam-business-*、yidun-cms)的契约入口,先注册再启消费方,避免 Dubbo 引用解析失败。cdc—— 统计/CDC 消息入口,只读上游 Kafka、不依赖dispatcher,先起可让统计链路早于审核池生效。dispatcher—— 审核池 ES 的唯一写入方;需在审核台可用前启动,否则console/api查到空池。注意cdc的TextAuditConsumer/ImageAuditConsumer会再生产Antispam_Audit_Msg,二者是上下游。console/api—— 审核员操作面,依赖 DB + Redis Session +business.client读 PRODUCT/TARGET 配置(Bean 创建阶段即拉取,见第七节)。scheduler—— 依赖审核池与业务数据已存在,且启动时立刻恢复 Redis 延迟任务队列(RedisDelayTask),须在 Redis 就绪后启动。facade/http-api/console/web—— 开放接口骨架与前端静态资源,最后(facade/http-api实际不在构建链内)。
四、逐服务启动逻辑
console/api
- 启动类:
com.netease.is.antispam.cms.console.api.ConsoleApplication(console/api/src/main/java/com/netease/is/antispam/cms/console/api/ConsoleApplication.java) @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)—— 只排除 Spring 自动数据源,未排除 Druid 自动配置@Enable*全量:@EnableBusinessClient、@EnableOssClient、@EnableFileParser、@EnableApolloConfig、@EnableEventClient、@EnableRecoverComponent、@EnableConfigurationProperties(CmsLoginUrlProperties.class)@Import关系(注解驱动,Configuration 定义在外部 starter 中):
| 注解 | @Import 的 Configuration | 关键能力 |
|---|---|---|
@EnableBusinessClient | BusinessClientConfiguration | 按条件建 BusinessHttpClient(business.client.options.remote.address)或 BusinessZookeeperClient(business.client.options.zookeeper.address,root-path=/netease-antispam/online/config);init() → 注册 Listener<?> → autoStart=true 时 start(),即在 Bean 创建阶段拉 PRODUCT/TARGET 配置 |
@EnableEventClient | EventClientConfiguration | EventHttpClient(条件 event.client.options.protocol.http.address,配置为 yd-mp-event-jd.service.163.org) |
@EnableFileParser | FileParseConfiguration | file.parse.timeout/callback.url/submit.url |
@EnableOssClient / @EnableRecoverComponent / @EnableApolloConfig | starter 内 | OSS 客户端、失败重放组件、Apollo 配置注入 |
- Apollo:
app.id=cms-console-api,apollo.bootstrap.namespaces=application,application.yml,common-config,apollo.cache-dir=./apollo/cache - Web:
server.port=8908、server.http2.enabled=true、server.tomcat.threads.max=400、basedir=./logs、max-http-form-post-size=576716800;spring.servlet.multipart.max-file-size/max-request-size=500MB - Session:
spring.session.store-type=redis、namespace=antispam-cms-api、cookieNTESwebSI(max-age: 1000d)、spring.session.timeout=8h - 其它:
spring.cloud.sentinel.enabled=true、log.dir=./logs/sentinel/;file.parse.submit.url=http://as.test.dun.163.com/v1/file/parse/submit main():仅SpringApplication.run(...)- 启动钩子:
StrategyRecordController implements InitializingBean(controller/record/);未发现ApplicationRunner/CommandLineRunner/@PostConstruct/@Scheduled
facade/dubbo-provider
- 启动类:
com.netease.is.antispam.cms.facade.dubbo.DubboApplication @SpringBootApplication(无 exclude)+@EnableAspectJAutoProxy+@EnableBusinessClient- Dubbo:
dubbo.application.id=antispam-cms-dubbo-provider、dubbo.protocol.id/name=dubbo、registry.protocol=zookeeper、protocol.register=false、scan.basePackages=com.netease.is.antispam.cms.facade.dubbo.provider、provider.filter=-validation,qos-access、application.qos-enable=false - Kafka Consumer 同进程开启:
netease.kafka.consumer.enable=true、groupId=antispam-cms、concurrency=10、enable.auto.commit=false、max.poll.records=100 spring.application.name=antispam-cms-dubbo-provider;main()仅 run;启动钩子未发现
dispatcher
- 启动类:
com.netease.is.antispam.cms.dispatch.DispatcherApplication @SpringBootApplication(无 exclude)+@EnableBusinessClient+@EnableRateLimiter+@EnableOssClient+@EnableEventClient- 消费者:
MediaCensorDispatchConsumer(@KafkaConsumer(topics={"Antispam_Audit_Msg"}, concurrency=4))→CensorDispatcher→EsIndexExecutor写 ES 审核池 - Kafka 参数:
groupId=netease-antispam-cms-dispatcher、concurrency=3(properties 层)、enable.auto.commit=true、max.poll.records=100 - 限流:
rate.limiter.node-size=1、rate.limiter.ratio=1.2;spring.cloud.compatibility-verifier.enabled=false main()仅 run;启动钩子未发现
cdc
- 启动类:
com.netease.is.antispam.cms.cdc.CdcApplication @SpringBootApplication(无 exclude)+@EnableApolloConfig+@EnableBusinessClient+@EnableRateLimiter+@EnableOssClient+@EnableRecoverComponent- 消费者:
CdcConsumer消费Antispam_Cdc及Antispam_Cdc_Text/Audio/Video_New/VideoSolution/Live_Video_New/LiveAudio_New/LiveVideoSolution/FileSolution/CrawlerResource/List/Image(clusterIds={"jd"});TextAuditConsumer/ImageAuditConsumer处理后生产Antispam_Audit_Msg - Kafka 参数:
groupId=netease-antispam-cms-cdc、concurrency=6、enable.auto.commit=false、max.poll.records=100 - Apollo:
app.id=antispam-cms-cdc、apollo.bootstrap.namespaces=application,application.yml main()仅 run;启动钩子未发现
scheduler
- 启动类:
com.netease.is.antispam.cms.scheduler.SchedulerApplication @SpringBootApplication(无 exclude)+@EnableElasticJob+@EnableBusinessClient+@EnableOssClient- ElasticJob:ZK
zk-0/1/2.zookeeper.yidun-infra:2181、namespaceantispam-cms-scheduler-jobs-online;40+ job,例如cmsNewNoticeWorker、openCloseServiceWorker、messageSendEmailWorker、*MonitorWorker、keywordHitNoticeWorker、cmsMiGuLiveBarrageCountWorker、cmsCheckFailDataWorker(cron=0 */1 * * * ?、overwrite=true)、liveVideoReviewCensorExpireTask(0 */1 * * * ?)、shortVideoSolutionMonitorWorker(*/30 * * * * ?)、cmsCensorIntegrationMonitorStatReportWorker(0 30 */1 * * ?);多数声明overwrite=true - Dubbo:
dubbo.application.id=antispam-cms-scheduler、consumer.retries=1;cms.console.api.url=http://yidun-cms-console-dev-1.jd.163.org - 启动钩子:
RedisDelayTask implements ApplicationRunner(tasks/delay/RedisDelayTask.java)——run()取RBlockingQueue(RedisDelayEventConstants.REDIS_DELAY_EVENT_QUEUE,StringCodec)并起守护线程poll(10s),即启动就恢复 Redis 延迟任务队列;开关cms.schedule.delay-event.handler=redis
facade/http-api(有启动类,不构建)
- 启动类:
com.netease.is.antispam.cms.facade.http.HttpApplication @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)+@EnableBusinessClientserver.port=8918;spring.application.name=antispam-cms-http-api;Kafka producer 开启- 无
Dockerfile、无build.sh,仅 dev/jiande-online/online/test 四套 properties(无application-private.properties) main()仅 run;启动钩子未发现
console/web(前端,不构建)
- 无 Java 启动类、无
@Enable*;package.jsonnameantispam-cms-web,version0.1.0,volta.node=18.20.3 - scripts:
dev/dev:private/build:test/build:private/build:private-test/build:production/test(vitest)/lint - 构建脚本仅
buildAll.sh(与父级同构,但目录下没有build.sh)与deploy.sh;部署形态由location.conf与kubernetes.yml描述;无Dockerfile
五、启动前置依赖
| 依赖 | 配置键 / 地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
| Apollo | app.id=cms-console-api(Apollo 侧项目名)/antispam-cms-cdc;namespaces=application,application.yml,common-config;apollo.cache-dir=./apollo/cache | 阻塞 | console/api/application.yml、cdc/application.properties |
| TiDB | spring.datasource.master.url=jdbc:mysql://tidb-cluster0-tidb.tidb.svc:4000/antispam(mirror 同 URL,com.mysql.jdbc.Driver) | 阻塞 | console/api/application-private.yml |
| Redis | spring.session.store-type=redis、namespace=antispam-cms-api、cookie NTESwebSI(max-age 1000d)、spring.redis.sentinel.master=master01、nodes=redis0-redis-ha.yidun-infra.svc:26379;console/api 另有 redis.multiple.configs.ai(10.200.128.26:6001,NetEase) | 阻塞 | application.yml + application-private.yml |
| Kafka(jd / jdlt 双集群) | netease.kafka.producer.cluster.jd.bootstrap.servers=kafka-yidun-{0,1,2}.kafka-yidun-headless.yidun-infra:9092;netease.kafka.producer.group.default=jd、group.music=jdlt | 弱(异步) | 各单元 properties/yml |
| ZK(Dubbo + business.client) | dubbo.registry.address=zookeeper://zk-0.zookeeper.yidun-infra:2181?backup=zk-1...,zk-2...,group /yidun/antispam/online-new/yidun-antispam-dubbo;business.client.options.zookeeper.address=...、root-path=/netease-antispam/online/config | 弱(Dubbo)/ 阻塞(配置读取) | application-private.yml |
| Elasticsearch | elasticsearch.cluster.servers=http://yidun-db-middleware-dev-{1,2,3}.jd.163.org:7000 | 弱(dispatcher 写、console/api 读) | application.yml、cdc/application.properties |
| OSS / NOS | oss.client.default-instance=yidun-antispam;实例 yidun(nos.netease.com)、yidun-antispam/yidun-antispam-archive/yidun-antispam-exam(nos-jd.163yun.com)、yidun-antispam(nos-eastchina1.126.net);oss.client.provider=netease | 弱 | application.yml、scheduler/application.properties |
| 上游 HTTP(检测/解决方案) | check.url.imageHosts/textHosts/audioHosts/vsHosts/lvsHosts → antispam-business-*-facade-http-check-api.yidun.svc、antispam-video-solution-http-api.yidun.svc、10.200.130.200:18166;file.solution.submit.url、video.solution.submit.url、livewall.solution.submit.url | 弱(调用期) | application-private.yml |
| 上游 HTTP(导出/消息/事件/文本特征) | yidun.export.server=http://yidun-export-http-api.yidun.svc、yidun.msg.api.url=http://10.200.130.200:8909、event.client.options.protocol.http.address=yd-mp-event-jd.service.163.org、textFeature.url=http://cluster-api.yidun-text.svc/dataservice/cluster | 弱(调用期) | application-private.yml |
| 私有化免登 | `cms.login.urlMap[censor | management | aiAdmin]、migu.logout.redirectUrl/externalUrl、private.secret` |
六、启动参数与 Profile
- Spring profile:
dev/private/test/online/jiande-online/jiande-pre - 形态不统一:
console/api是唯一用.yml的单元(application.yml+application-{dev,test,private,jiande-online,jiande-pre}.yml),dispatcher/cdc/scheduler/facade/dubbo-provider用.properties;facade/http-api缺application-private.properties - maven profile:由
buildAll.sh的GLOBAL_ENV=private注入mvn clean install -Pprivate;各build.sh内BUILD_ENV=private - JAVA_OPTS:5 个 Dockerfile 统一
-Xmx1024m -Xms1024m -XX:+UseG1GC -Dspring.profiles.active=private - base image 与仓库:
private-registry.yidun.internal/yidun/base-image:jdk17;build.shDOCKER_REPOSITORY=private-registry.yidun.internal/yidun、DOCKER_IMAGE_TAG=migu-231016 - 构建方式:
sudo docker build(非 buildx),因此本模块镜像只有 amd64,迁 ARM 需另改脚本 build.sh关键参数:MODEL_NAME=console/api|facade/dubbo-provider|dispatcher|cdc|scheduler、WITH_PULL=true、WITH_COMPILE=true、--build-arg GIT_COMMIT/BUILD_TIME/GIT_BRANCH(写入/app/version.txt)- Node 版本:Java 构建链不涉及;
console/web若单独构建需自备 Node(其volta.node=18.20.3,与antispam-cms-web的18.16.0不同)
七、启动期踩坑
ConsoleApplication只排除DataSourceAutoConfiguration,主库/从库 URL 完全相同(spring.datasource.master与mirror同为tidb-cluster0-tidb...),所谓读写分离实际不生效,压测时不要指望 mirror 分流。spring.profiles.active: dev写在application.yml/application.properties里:漏传-Dspring.profiles.active=private会静默读到 dev 配置(ES 指向localhost:7000、captcha.enabled=true、system.online.env=false),表现为「起了但查不到数据」。business.client.options.zookeeper.address未配置时BusinessClientConfiguration不创建任何 Bean(条件装配静默失效)→ 下游 Product/Target 配置全空,而启动日志无异常。RedisDelayTask是ApplicationRunner:scheduler启动即连 Redisson 拉延迟队列,Redis 不可用会直接启动失败(不是降级);cms.schedule.delay-event.handler=redis决定是否走该路径。facade/http-api占 8918 端口却无Dockerfile/build.sh,且缺application-private.properties,属「有入口未部署」的典型坑。console/api/build.sh首行残留rm -rf ../web/node/*:会删掉console/web/node(若存在),与前端构建互相干扰;该行无任何前置判断。build.sh用sudo docker build而非buildx(与antispam-cms-web/antispam-solution的双架构构建不同),迁 ARM 必须另写脚本。console/api无 Runner 而scheduler有:console/api侧只有StrategyRecordController implements InitializingBean这类实现细节,真正的启动期副作用集中在scheduler(RedisDelayTask),排查「启动后立刻异常」应先看scheduler。kubernetes.yml内容与模块不对应:仓库根与console/web/的kubernetes.yml描述的是antispam-business-image-*(如private-registry.yidun.internal/yidun/antispam-cms-image-dubbo-provider:1.0.0.private),不能作为本模块实际部署依据。cdc与dispatcher的 Kafkaenable.auto.commit不一致(cdc=false、dispatcher=true):cdc侧消息处理失败会停在原位重放,dispatcher侧则跳过,跨单元排查消息丢失时必须先确认是哪条链路。console/api的file.parse.callback.url指向https://cms-api.test.dun.163.com/grammrFix/callback/callBackParseResult:private 环境下回调地址与实际域名可能不匹配,语法纠错回调会不可达。scheduler的 ElasticJob namespace 写死antispam-cms-scheduler-jobs-online:多环境共用同一 namespace 时任务会互相覆盖,改 cron/overwrite会串环境。
八、跨模块前置
- 上游消息源:
antispam-business-{text,image,audio,video,liveaudio,livevideo}投递Antispam_Audit_Msg与Antispam_Cdc_*;antispam-bill投递Antispam_Bill_Package_Change(触发 CMS 服务开通/关闭)。未上线时dispatcher/cdc空跑,不阻塞启动。 - 下游契约消费方:
antispam-cms-web(前端)、yidun-cms、yidun-supervision-cms通过 Dubbo 查本模块账号/标签/模板/客户/服务配置;facade/dubbo-provider必须先注册。 - 检测与导出/消息能力:
antispam-business-*(HTTP check 接口,用于审核台内联检测)、antispam-video-solution、antispam-file-solution(antispam-file-solution-http-api.yidun.svc)、yidun-export(异步导出)、yidun-message(站内信)、yidun-accountcenter(账号中心)、mplatform-event(yd-mp-event-jd.service.163.org)。 - 基础设施硬前置:Apollo、TiDB、Redis(Session + Redisson)三者任一不可用,
console/api与scheduler直接启动失败;ES 不可用只影响审核池读写;ZK 不可用会使 Dubbo 与business.client配置读取失败(后者属启动期阻塞)。 - 私有化客户对接:免鉴权白名单路径(
/api/account/getToken、/api/account/sync/*、/api/account/logout)在SecurityConfiguration的web.ignoring()中,改动这些路径必须同步改白名单,否则客户接入全部 401。