外观
antispam-solution — 文档/文件反垃圾解决方案(提交 → 存储 → 解析 → 检测 → 回调 → 归档计费)
分层:业务层(易盾文档解决方案)| 部署单元:6 个(无模块级 kubernetes.yml)| 数据库:MySQL/TiDB + MyBatis-Plus(库名由 Apollo 下发,
type-aliases-package=com.netease.is.antispam.solution.domain)| base image:private-registry.nis.netease.com/library/base-image:jdk17(tagbase-250409-pg)
一、模块定位与架构
它解决的是「客户提交一个文档/文件(Word/Excel/PPT/PDF/OFD/压缩包/图片等),如何解析出其中的文本与图片、送反垃圾引擎判定是否含色情/广告/暴恐/违禁/涉政等内容,并把机审/人审结论可靠回调给客户、留档备查并据以计费」。
对外是易盾「文档/文件检测」产品(v1/v2 两套接口,V2 兼容 ProductType.DOCUMENT_SOLUTION / MEDIA_SOLUTION / BOOK_SOLUTION / REPORT_SOLUTION / CRAWLER_SOLUTION 五类解决方案),对内是一条由 Kafka 串起来的异步流水线,核心是 CheckStateMachine 的状态与「先落库才能解析、解析完才能检测」的顺序约束。
架构分层
- 协议层:
facade/http-check-api—— 检测提交入口/v1/file/submit、/v2/file/submit(SolutionFileCheckController,含 content 上传 OSS、策略组校验)facade/http-api—— 回调结果拉取/v1|v2/file/callback/results、解析提交(客户已有的人审结论抄送回流)facade/dubbo-provider—— 控制台 Dubbo:SolutionMainFacadeProvider、FileDataFacadeProvider、FileSolutionProvider、SolutionOpRecordFacadeProvider、FileOpRecordProvider、FileDataArchiveFacadeProvider
- 逻辑层:
service/business(CheckService状态机编排、SolutionCheckApp、CensorService/SolutionCensorApp、Submitter系列)+adapter(HTTP 请求→DTO、回调→各版本响应、CDC 消息适配) - 数据层:
service/base(DAO/ES/MyBatis Mapper/repo/CDC 同步)+storage(Kafka 消费落库、CDC、归档、主动回调) - 解析层:
parser/file(FileParseService+FileParseOptions+ 各类型解析器,独立部署单元) - 调度层:
scheduler(ElasticJob:审核纠正、超时监控、数据扫描、操作记录清理) - 公共层:
common(枚举常量,包名com.netease.is.antispam.solution.common)、domain(实体/BO/DTO/VO 与 MyBatis 模型)
关键数据流(异步检测)
SolutionFileCheckController.checkV1/V2→FileCheckAdapter.adapt(组装 FILE 子数据)→SolutionCheckApp.check(区分同步/异步、生成 taskId、组子数据)CheckService.sendStoreRequest(START → SEND_STORE_MSG)→ Kafkastorestorage的SolutionStoreConsumer落库solution_main_data(状态STORED)parser/file消费parse.submit→FileParseService解析文本/图片 → 回写解析结果CheckService.beginCheck提交底层检测(TextSubmitterV5/ImageSubmitterV5/VideoSolutionSubmitterV2)→ 底层回调InnerReceiveCallbackV2Controller→metaCheckCallbackstorage生成并主动回调客户 callbackUrl(SolutionActiveCallbackConsumer)+ 归档(SolutionArchiveConsumer)+ 发REQUEST_STAT/CENSOR_STAT给antispam-bill
状态机(CheckState)
START → RECEIVED → STORED →(需解析:PARSE_SUBMITTED → PARSE_FINISHED;解析失败:PARSE_FAILED;无需解析直接 PARSE_FINISHED)→ CHECK_SUBMITTED → INNER_CALLBACKED → CHECK_FINISHED → WAITING_CALLBACK → FINISHED。
其中 STORED 亦可经 FINISH_WITHOUT_ACTION 直接 FINISHED(无需解析/检测的数据直接结束);事件入口集中在 CheckService 的 sendStoreRequest / storeRequest / sendParseRequest / beginCheck / metaCheckCallback / finishCheck。
二、可部署服务清单
| 部署单元目录 | artifactId | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml |
|---|---|---|---|---|---|---|
facade/http-check-api | antispam-solution-facade-http-check-api | private-registry.nis.netease.com/yidun/antispam-solution-facade-http-check-api:base-250409-pg | 8123 | Spring Boot Web(检测提交入口) | 有 | 否(本模块无 kubernetes.yml) |
facade/http-api | antispam-solution-facade-http-api | private-registry.nis.netease.com/yidun/antispam-solution-facade-http-api:base-250409-pg | 8234 | Spring Boot Web(回调/查询/解析提交) | 有 | 否(同上) |
facade/dubbo-provider | antispam-solution-facade-dubbo-provider | private-registry.nis.netease.com/yidun/antispam-solution-facade-dubbo-provider:base-250409-pg | Dubbo port=-1(无 Web) | Dubbo Provider | 有 | 否(同上) |
storage | antispam-solution-storage | private-registry.nis.netease.com/yidun/antispam-solution-storage:base-250409-pg | 无 Web | Kafka Consumer(落库/CDC/归档/回调) | 有 | 否(同上) |
parser/file | antispam-solution-parser-file | private-registry.nis.netease.com/yidun/antispam-solution-parser-file:base-250409-pg | 无 Web | Kafka Consumer(文档解析) | 有 | 否(同上) |
scheduler | antispam-solution-scheduler | private-registry.nis.netease.com/yidun/antispam-solution-scheduler:base-250409-pg | 无 Web | ElasticJob | 有 | 否(同上) |
非独立部署类库(无 Dockerfile,不单独出镜像):common、domain、service/base、service/business、adapter、facade/dubbo-api。
构建脚本与产物部署方式
- 6 个单元全部是 Spring Boot(本模块无 WAR、无 XML 容器、无纯前端),全部有独立
Dockerfile+build.sh 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复用已编译产物;另有buildAll-arm.shbuild.sh套路:MODEL_NAME=facade/http-check-api|facade/http-api|facade/dubbo-provider|storage|parser/file|scheduler、BUILD_ENV=private、WITH_PULL=true、WITH_COMPILE=true;cd ../..→ 可选git pull→ 可选mvn clean install --pl ${MODEL_NAME} -am -T 1C -U -Dmaven.test.skip=true -P ${BUILD_ENV}→cd ${MODEL_NAME}→docker buildx build --platform linux/amd64,linux/arm64 --build-arg GIT_COMMIT/BUILD_TIME/GIT_BRANCH --tag <FULL_IMAGE_PATH> --push→source ~/.zsh_private && bash $k8s_script_path/podupdate.sh <name> <tag>- 镜像内落地:
COPY ./target/${APP_NAME}.jar /app/,ENTRYPOINT ["bash","docker-entrypoint.sh"] - 本模块没有
kubernetes.yml:部署清单在别处维护,build.sh里的podupdate.sh才是真正的发布动作
三、同模块启动顺序
storage—— 状态机要求「先落库STORED才能解析」:它是 Kafkastore的唯一消费方,不先起则提交的文档永远停在START/SEND_STORE_MSG。parser/file—— 只消费落库后发出的parse.submit;依赖上游解析产物回写,故排在storage之后。facade/http-check-api—— 客户提交入口,需 DB 可写 + Kafka 可发;早于它启动则提交的消息无人消费。facade/http-api—— 客户主动拉取回调结果与解析提交,依赖已落库数据。facade/dubbo-provider—— 控制台(antispam-cms)的审核/查询/解锁入口,需要数据已存在才有意义。scheduler—— 超时监控/审核纠正/数据扫描,最后启动,避免对空库做扫描与告警。
四、逐服务启动逻辑
facade/http-check-api
- 启动类:
com.netease.is.antispam.solution.facade.http.check.HttpCheckApplication(facade/http-check-api/src/main/java/com/netease/is/antispam/solution/facade/http/check/HttpCheckApplication.java) @SpringBootApplication:无 exclude@Enable*全量:@EnableRateLimiter、@EnableBusinessClient、@EnableOssClient、@EnableI18nComponent、@EnableIsolateComponent、@EnableRecoverComponent- 无自定义
@Import;校验与监控由本地类承担:FacadeHttpCheckConfiguration、aop/validation/ApiCheckInterceptor、aop/monitor/ApiMonitorInterceptor、GlobalExceptionHandler - 端口与上传:
server.port=8123、server.http2.enabled=true、server.tomcat.basedir=./logs、spring.servlet.multipart.max-file-size/max-request-size=20MB、server.tomcat.max-http-form-post-size=20971520 - Apollo:
apollo.bootstrap.enabled=true、apollo.bootstrap.namespaces=http-check,application,antispam-solution-common、apollo.cache-dir=./apollo/cache、apollo.cluster=default - OSS:
oss.client.default-instance=yidun-antispam-intranet(protocol=http、endpoint=nos-jd.service.163.org、bucketyidun-antispam-solution、cdn-domain=yidun-antispam-solution.nos-jd.163yun.com) - Kafka producer 开启(
netease.kafka.producer.enable=true,clusterdefault,compression.type=snappy等);Sentinelspring.cloud.sentinel.enabled=true、log.dir=./logs/sentinel/ - maven 资源过滤占位:
spring.application.name=@filter.sentry.cluster.name@、recover.config.appName=@filter.sentry.cluster.name@ main():仅SpringApplication.run(...)- 启动钩子:未发现任何
ApplicationRunner/CommandLineRunner/@PostConstruct
facade/http-api
- 启动类:
com.netease.is.antispam.solution.facade.http.HttpApplication @SpringBootApplication(无 exclude)+@EnableBusinessClient+@EnableI18nComponent+@EnableIsolateComponent+@EnableRecoverComponent- 无
@EnableOssClient、无@EnableRateLimiter(与http-check-api的关键差异) - 端口:
server.port=8234;上传限制同为 20MB;Apollo namespacesapplication,antispam-solution-common main()仅 run;启动钩子未发现
facade/dubbo-provider
- 启动类:
com.netease.is.antispam.solution.facade.dubbo.DubboApplication @SpringBootApplication(无 exclude)+@EnableAspectJAutoProxy+@EnableBusinessClient+@EnableRecoverComponent+@EnableDistributeId+@EnableOssClient+@EnableCdcDataSubscribe@EnableDubboAutoConfiguration是本模块自研注解(.../facade/dubbo/configuration/EnableDubboAutoConfiguration.java):元注解@Target(TYPE)+@Retention(RUNTIME)+@Documented+@Component+@Import(DubboConfiguration.class)DubboConfiguration的装配细节:@Configuration@ComponentScan(value = {"com.netease.is.antispam.solution.config", "com.netease.is.antispam.solution.business.censor.service", "com.netease.is.antispam.solution.repo", "com.netease.is.antispam.solution.dao", "com.netease.is.antispam.solution.business.common.redis", "com.netease.is.antispam.components.callback", "com.netease.is.antispam.solution.component.es", "com.netease.is.antispam.components.kafka", "com.netease.is.antispam.solution.business.check.config", "com.netease.is.antispam.solution.adapter", "com.netease.is.antispam.solution.business.common.ratelimit", "com.netease.is.antispam.solution.business.common.recover", "com.netease.is.antispam.solution.cdc"}, basePackageClasses = {ProductFieldService.class, TrialCheckAmountLimiter.class})@MapperScan("com.netease.is.antispam.solution.dao")@Bean public RedisComponent redisComponent()
- Dubbo 配置:
dubbo.application.id=antispam-solution-dubbo-provider、application.dump-directory=./logs、protocol.port=-1、scan.basePackages=com.netease.is.antispam.solution.facade.dubbo、provider.payload=83886080(80MB)、application.qos-enable=false - Apollo namespaces
dubbo,application,antispam-solution-common main()仅 run;启动钩子未发现
storage
- 启动类:
com.netease.is.antispam.solution.storage.StorageApplication @SpringBootApplication(无 exclude)+@EnableCdcDataSubscribe+@EnableBusinessClient+@EnableOssClient+@EnableIsolateComponent+@EnableRecoverComponent+@EnableDistributeId- Kafka Consumer:
concurrency=3、enable.auto.commit=false、max.poll.records=100、max.poll.interval.ms=1800000(30 分钟)、max.partition.fetch.bytes=20485760;producer 亦开启 - 消费链路:
SolutionStoreConsumer、SolutionCdcConsumer、SolutionArchiveConsumer、SolutionInnerCallbackConsumer、SolutionActiveCallbackConsumer - Apollo namespaces
application,antispam-solution-common;recover.config.appName=@filter.sentry.cluster.name@ main()仅 run;启动钩子未发现
parser/file
- 启动类:
com.netease.is.antispam.solution.parser.file.ParserFileApplication @SpringBootApplication(无 exclude)+@EnableOssClient+@EnableConfigurationProperties({FileParseProperties.class})+@EnableRecoverComponent+@EnableIsolateComponent+@EnableBusinessClient- 消费链路:
FileParseService/InnerFileParseConsumer/FileParseOptions/FileType分发各类型解析器 - Kafka Consumer:
concurrency=8、enable.auto.commit=false、max.poll.records=20、max.poll.interval.ms=600000(10 分钟) - recover 参数:
recover.config.recoverThreshold=15、recoverFileSizeLimitInMB=-1、clusterCleanIntervalInSec=60、recoverFailRetryIntervalInSec=5、recoverLinesPerTime=100、recoverFailAbortEnable=false、clusterRecoverPoolSize=2 - OSS 双实例:
yidun(nos.netease.com)与yidun-antispam(nos-jd.163yun.com,bucketyidun-antispam-solution),默认实例yidun - Apollo namespaces
file-parser,application,antispam-solution-common;spring.aop.proxy-target-class=true main()仅 run;启动钩子未发现
scheduler
- 启动类:
com.netease.is.antispam.solution.scheduler.SchedulerApplication @SpringBootApplication(无 exclude)+@EnableBusinessClient+@EnableOssClient+@EnableIsolateComponent+@EnableRecoverComponent+@EnableScheduling+@EnableElasticJob+@EnableDistributeId- 任务:
elastic.job.checkScanWorker.overwrite=true、elastic.job.checkScanWorker.cron=0 0/1 * * * ?(每分钟扫描超时/待处理数据) - Apollo namespaces
scheduler,application,antispam-solution-common - 模块内另有
listener/YidunQosApplication(Qos 限流注册) main()仅 run;启动钩子未发现(@EnableScheduling仅开启注解驱动,未在启动类挂任务)
五、启动前置依赖
| 依赖 | 配置键 / 地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
| Apollo | apollo.bootstrap.enabled=true、apollo.cache-dir=./apollo/cache、apollo.cluster=default;namespaces 按单元不同(http-check,application,antispam-solution-common / file-parser,... / scheduler,... / dubbo,...) | 阻塞(库连接、密钥等均可能来自 Apollo) | 各单元 src/main/resources/application.properties |
| MySQL / TiDB | mybatis.mapper-locations=classpath*:mybatis/mappers/*.xml、type-aliases-package=com.netease.is.antispam.solution.domain;仓库内 properties 未声明 spring.datasource,数据源由 Apollo 下发 | 阻塞 | 各单元 application*.properties + Apollo |
| OSS / NOS | oss.client.provider=netease;yidun-antispam-intranet → http://nos-jd.service.163.org,bucket yidun-antispam-solution;解析器另有 yidun/yidun-antispam | 弱(提交/解析期) | http-check-api/application.properties、parser/file/application.properties |
Kafka(cluster default / jd) | netease.kafka.producer.enable=true(store、parse.submit、Antispam_Bill_CensorStat);storage/parser/file 消费侧 enable.auto.commit=false | 弱(异步);上游链路实际强依赖 | 各单元 application.properties |
| Elasticsearch | CDC 同步与检索(EnableCdcDataSubscribe + component.es) | 弱 | Apollo + service/base |
| Redis | RedisComponent(由 DubboConfiguration 显式 @Bean 注册,solution.business.common.redis 包扫描) | 弱(缓存/去重) | facade/dubbo-provider 的 DubboConfiguration + Apollo |
| 底层检测 Submit | antispam-textclassify(TextSubmitterV5)/ antispam-image(ImageSubmitterV5)/ antispam-video-solution(VideoSolutionSubmitterV2),经 HTTP 签名提交 | 弱(检测期阻塞单条) | Apollo |
| 业务配置源 | antispam-business:ProductCache/ClientSceneCache/ProductConfigCache/SecretInfoCache/TargetCache/TargetConfigCache;business.client.options.operateTargetTypes=PRODUCT,PRODUCT_CONFIG | 弱(检测期) | business.client.options.* + Apollo |
| 计费 | antispam-bill:REQUEST_STAT(yidun_request_stat)、CENSOR_STAT(Antispam_Bill_CensorStat) | 弱(异步) | Apollo |
六、启动参数与 Profile
- Spring profile:
dev(默认,写在application.properties) /test/online;仓库没有application-private.properties - 环境差异载体是 maven 资源过滤:
src/main/filter/{default,test,online,private}.properties(注意目录是src/main/filter,不是src/main/resources/filter),application.properties里用spring.application.name=@filter.sentry.cluster.name@、recover.config.appName=@filter.sentry.cluster.name@占位 - maven profile:
buildAll.sh固定GLOBAL_ENV=private→mvn clean install -Pprivate;各build.sh内BUILD_ENV=private、WITH_PULL=true、WITH_COMPILE=true - JAVA_OPTS:6 个 Dockerfile 统一
-Xmx512m -Xms512m -XX:+UseG1GC -Dspring.profiles.active=private - base image 与仓库:
private-registry.nis.netease.com/library/base-image:jdk17;DOCKER_REPOSITORY=private-registry.nis.netease.com/yidun,DOCKER_IMAGE_TAG=base-250409-pg - 构建平台:
--platform linux/amd64,linux/arm64(双架构一次推送),随后podupdate.sh <name> <tag> - Node 版本:不适用(无前端单元)
七、启动期踩坑
-Dspring.profiles.active=private但仓库无application-private.properties:环境差异全靠 maven@filter.*@资源过滤;漏掉-P private会让@filter.sentry.cluster.name@原样进入配置(应用名变成一串占位符字面量),Sentry 上报与日志目录都会错。- 端口硬编码:
http-check-api8123、http-api8234 直接写在application.properties;多实例或本地起两份必然端口冲突。 @EnableDubboAutoConfiguration是本模块自实现注解,其@ComponentScan是一份手写白名单(13 个包 +basePackageClasses={ProductFieldService, TrialCheckAmountLimiter}):新增包未列入即 Bean 缺失,且无编译错误,只在运行期暴露空指针/无实现。recover.config.recoverFailAbortEnable改动风险高:parser/file与http-check-api均为false;若照抄antispam-cms的true(那边确为true),console 场景下失败会直接中断重放链。storage/parser/file的enable.auto.commit=false+ 超长max.poll.interval.ms(storage 1800000ms、parser 600000ms):单条消息处理超时会表现为 “消息卡住不动”而非报错,Kafka 侧只看到 rebalance,排查必须结合max.poll.records(storage 100 / parser 20)。- 解析器依赖内网 OSS 域名(
nos-jd.service.163.org走protocol=http):不可达时表现为超时而非明确报错;默认实例是yidun-antispam-intranet,误配成公网nos-jd.163yun.com会长时间重试。 - 本模块没有
kubernetes.yml:部署清单在别处维护,只跑docker buildx不会更新 Pod,必须执行podupdate.sh。 http-api缺@EnableOssClient:它不注册 OSS 客户端,若后续在http-api里新增需要 OSS 的逻辑会直接在启动期报 Bean 缺失(而不是运行期)。basePackageClasses里塞了业务类(ProductFieldService、TrialCheckAmountLimiter):这两个类一旦挪包,组件扫描根路径会随之漂移,属隐性耦合。@EnableIsolateComponent出现在 5 个单元(除facade/dubbo-provider外的全部):Kafka 隔离组件依赖components.kafka的扫描路径,与DubboConfiguration的@ComponentScan存在重叠,重复注册时以先注册者为准,排查限流/隔离失效需先确认是哪个进程加载的。dubbo.provider.payload=83886080(80MB):Dubbo 单次传输上限被放大到 80MB,文件元数据/子数据量大时会放大内存与网络开销,-Xmx512m的堆在并发高时偏紧。scheduler的checkScanWorker每分钟执行:cron=0 0/1 * * * ?+overwrite=true,多副本部署时该 job 无分片保护(依赖 ElasticJob 分片),误扩副本会重复扫描。
八、跨模块前置
- 上游触发方:
- 外部客户(易盾 SDK / HTTP 客户端)直接调用
facade/http-check-api的/v1|v2/file/submit与facade/http-api的回调结果拉取、解析提交接口(inferred,产品入口) antispam-cms/antispam-cms-web(控制台)经facade/dubbo-provider的SolutionMainFacadeProvider、FileDataFacadeProvider、FileSolutionProvider、SolutionOpRecordFacadeProvider、FileOpRecordProvider、FileDataArchiveFacadeProvider做审核/查询/解锁/操作记录查询(inferred)
- 外部客户(易盾 SDK / HTTP 客户端)直接调用
- 下游依赖:
antispam-business——ProductCache/ClientSceneCache/ProductConfigCache/SecretInfoCache/TargetCache/TargetConfigCache与ProductType/BusinessType/CheckAbilityType/TargetType枚举(extracted)antispam-bill——REQUEST_STAT(yidun_request_stat)、CENSOR_STAT(Antispam_Bill_CensorStat)计量消息,提交器使用其RequestStatMessage/AreaEnum/RegionEnum(extracted)antispam-textclassify/antispam-image/antispam-video-solution—— 解析出的文本/图片/音视频分别经TextSubmitterV5/ImageSubmitterV5/VideoSolutionSubmitterV2提交底层检测引擎(inferred,经 HTTP 签名提交)antispam-common——ApiVersion/TargetType/ResultStatus等常量与FastJsonUtil(extracted)antispam-components—— 状态机state.machine.core、OssClient、CDC 组件、i18n、components.callback、components.kafka(extracted)
- 基础设施硬前置:Apollo 与 MySQL 缺失时 6 个单元都无法启动;Kafka 中断会让整条异步流水线停在
SEND_STORE_MSG/PARSE_SUBMITTED;OSS 不可达会让解析与归档链路超时。 - 顺序建议:
antispam-business(配置与策略)→antispam-bill(计量)→ 底层检测引擎(antispam-textclassify/antispam-image/antispam-video-solution)→ 本模块storage→parser/file→http-check-api→http-api→dubbo-provider→scheduler。