主题
模块划分与 Maven 依赖
8 个业务模块 + 1 个聚合父 pom:依赖方向单向向下,
captcha-common零框架依赖,captcha-base是唯一数据访问出口。
1. 依赖治理总览
根 pom.xml 是纯聚合 + 版本治理父工程:groupId=com.netease.is、artifactId=netease-captcha-custom、version=1.2.0-SNAPSHOT、packaging=pom,通过 <modules> 聚合 8 个子模块(pom.xml:7-20)。全部第三方版本集中在 <properties>(pom.xml:22-173),再由 <dependencyManagement> 统一锁版本,子模块只声明 groupId/artifactId,不写 <version>——唯一例外是内部模块依赖,一律写 <version>${project.version}</version>。
两处命名不一致必须注意,否则打包脚本会写错:
| 模块目录 | artifactId | 是否与目录同名 |
|---|---|---|
captcha-base | captcha-custom-base | 否 |
captcha-server | captcha-custom-server | 否 |
captcha-admin | captcha-custom-admin | 否 |
captcha-common | captcha-custom-common | 否 |
captcha-image | captcha-custom-image | 否 |
captcha-schedule | captcha-custom-schedule | 否 |
captcha-demo | captcha-demo(且自带 groupId com.netease.is.nc 与独立 version 0.0.1) | 是 |
captcha-test | captcha-custom-test | 否 |
captcha-demo/pom.xml:13-15 覆写了 groupId 与 version,是唯一「脱管」的模块——父 pom 的 mvn versions:set 不会改到它,版本升级时容易漏。
2. 逐模块 pom 依赖分析
2.1 captcha-common — 契约层(jar)
内部依赖:无(这是全仓唯一零内部依赖模块,captcha-common/pom.xml:14-97 只有第三方)。
| 第三方 | 用途 |
|---|---|
com.netease.is.base:nisp-commons | 内部基础工具(排除 nos-sdk-java) |
location-svc-lib | 地域解析(排除 log4j 两个坐标) |
com.github.ua-parser:uap-java | User-Agent 解析 |
TarsosLSH-latest | 相似图片局部敏感哈希 |
com.netease.yidun:yidun-common-utils | 内部加解密(NIS / SM2) |
joda-time、org.json、jackson-annotations、lombok | 时间、JSON、多态序列化注解 |
javax.servlet-api | ServletUtils 等工具签名需要 |
log4j-jcl / log4j-slf4j-impl / log4j-core / log4j-1.2-api / disruptor | 统一日志门面与异步 appender |
junit | 单元测试 |
设计约束:本模块只放跨模块共享契约(错误码、命中码、名单枚举、CaptchaInfo 模型、算法工具),禁止引入 Spring/DB 运行时依赖;一旦引入,所有模块都被迫携带。
2.2 captcha-base — 基础设施层(jar)
内部依赖:captcha-custom-common(${project.version},排除 commons-collections、log4j,captcha-base/pom.xml:26-39)。
| 第三方 | 用途 |
|---|---|
aws-java-sdk-s3 | S3UploadService(当前唯一生效的图片上传实现) |
jedis + spring-data-redis | Redis 客户端与模板 |
mybatis + mybatis-spring + druid | DAO 层与连接池 |
mysql-connector-java + panweidb-jdbc | 两种数据库方言驱动 |
elasticsearch-java + jakarta.json-api | ES 行为日志读写 |
spring-context / spring-orm / spring-aspects / spring-webmvc + javax.servlet-api | 基础设施上下文(含 @Scheduled 所需 task:annotation-driven) |
mi-utils-img4j + filters | 题面图片加工算法 |
javax.mail | 告警邮件 |
bcprov-jdk15on | License RSA / SM2 加解密 |
kryo | 模型/缓存对象序列化 |
caffeine | 本地缓存 |
httpmime | multipart 上传 |
general-tools-utils、nisp-commons | 内部工具 |
构建细节:maven-jar-plugin 把 conf/** 从产物中排除(captcha-base/pom.xml:207-217),所以 src/main/resources/conf/base.properties.template 不进 jar,外部配置模板只能从源码目录取。
2.3 captcha-server — 核心业务层(war)
内部依赖:captcha-custom-base(排除 commons-lang3)+ captcha-custom-common(排除 log4j、commons-collections)(captcha-server/pom.xml:20-46)。
| 第三方 | 用途 |
|---|---|
spark-mllib_2.12 | 风险模型推理/训练,附 30+ 条 exclusion 剥掉 Hadoop/Orc/Parquet 传递依赖(captcha-server/pom.xml:135-280) |
netease-captcha(com.netease.is.image) | 内部验证码图形算法包 |
nisp-commons-evaluator | 规则表达式求值 |
simpleclient / simpleclient_hotspot / simpleclient_servlet | /metrics 暴露 JVM 与业务指标 |
ip-idc-local-api | 代理 IP / IDC 判定(排除 log4j、commons-lang3) |
json-path、UserAgentUtils、hibernate-validator、commons-net、netty-all、qy-qos-web | 解析、校验、FTP、QOS 防护 |
freemarker | 错误页模板(/errors/*.html) |
aspectjweaver / aspectjrt / cglib / spring-aop | AOP 代理 |
构建插件三连:scala-maven-plugin 在 process-resources 阶段先编译 Scala(因为 Java 依赖 Scala 代码,pom.xml:330-350)、tomcat7-maven-plugin(8085)、jetty-maven-plugin(8080)、dockerfile-maven-plugin(仓库地址硬编码 + 明文 registry 账号,见风险 R-08)。
2.4 captcha-admin — 运营配置层(war)
内部依赖:仅 captcha-custom-base(排除 log4j,captcha-admin/pom.xml:16-26)。
| 第三方 | 用途 |
|---|---|
spring-security-core / -config / -taglibs | 后台认证与 URL 级授权、页面 <sec:authorize> |
freemarker | 后台页面渲染 |
quartz | 版本声明(实际调度走 @Scheduled) |
hibernate-validator | 表单校验 |
poi + poi-ooxml + poi-ooxml-full + jxl + xml-apis | 日志/统计 Excel 导入导出 |
mi-utils-img4j | 图片集管理页的缩略与裁剪 |
commons-fileupload / commons-net | 文件上传、FTP 同步 |
qy-qos-web、netty-all | QOS 防护 |
spring-context-support | 邮件等集成 |
构建:finalName=captcha-admin、上下文路径 /mgt/decision-captcha/v2/admin(pom.xml:156-166)。
2.5 captcha-image — 图片存储层(war)
内部依赖:无。这是最容易被误判的一点:captcha-image/pom.xml:15-101 里没有任何 captcha-custom-* 依赖,它是一个完全独立的静态资源 + 上传服务,只依赖 spring-webmvc、commons-fileupload、commons-net(多节点同步)、freemarker、netty-all、qy-qos-web、jackson-databind、guava、commons-lang3、commons-io。
含义:升级 captcha-base 不需要重新构建 captcha-image,反过来也不存在共享缓存/DAO 的耦合;它与 captcha-schedule 之间只有 HTTP 上传协议(UploadController + TokenUtils 鉴权)。
maven-war-plugin 把仓库根的 icons/、imgs/、space/ 目录作为 webResources 拷到 WEB-INF/image(pom.xml:123-158),所以初始素材是随 WAR 发布的,而不是运行时下载。
2.6 captcha-schedule — 离线任务层(jar)
内部依赖:captcha-custom-base,且显式排除 spring-webmvc、spring-web、junit(captcha-schedule/pom.xml:15-34)——排除 web 是为了让常驻进程不带 Servlet 容器依赖;排除 junit 是为了避免测试框架被打进生产 lib(注释明确写「触发 CVE-2020-15250」)。
| 第三方 | 用途 |
|---|---|
netty-all | 内部 HTTP/长连接(部分素材分发) |
qy-qos-javaapp | 应用级 QOS |
javax.servlet-api(compile,源码注释:解决 Spring AOP 对 Web 类的依赖,pom.xml:44-49) | 编译期兜底 |
构建:maven-dependency-plugin:copy-dependencies 在 package 阶段把所有依赖复制到 target/lib(pom.xml:77-92),Docker 构建再用两阶段把 target/lib 加密成 lib.enc(captcha-schedule/Dockerfile:9-24)。因此改动任何依赖坐标都会改变镜像结构,需同步验证 entrypoint.sh 的 classpath。
2.7 captcha-demo — 示例层(war)
内部依赖:无。只依赖 qy-qos-web、commons-codec、httpclient、commons-lang3、fastjson、servlet-api(provided)(captcha-demo/pom.xml:22-48)。它通过 HTTP 调 captcha-server 与 captcha-image,因此不共享任何模型类;NECaptchaVerifier 里的 ValidateResult 是它自己复刻的一份。编译器配置为 1.7(pom.xml:56-57),是全仓唯一非 1.8 的模块。
2.8 captcha-test — 测试层(jar,shade)
内部依赖:无。全部为 guava、fluent-hc、httpclient、commons-codec、commons-lang3、fastjson、netty-all、lombok + selenium-java(排除 4 个传递依赖)+ webdrivermanager + browsermob-core(抓包)+ javacv-platform / opencv / javacpp + testng(captcha-test/pom.xml:14-158)。
maven-shade-plugin 以 auto.Main 为 Main-Class 打出可执行包(pom.xml:214-251),并把 META-INF/spring.handlers、spring.schemas 合并——这一步是必须的,否则 shutdown 后的依赖类不完整。maven-enforcer-plugin 的 dependencyConvergence 规则写好了但 skip=true(pom.xml:253-277),意味着当前不做依赖收敛校验。
3. 模块依赖矩阵
行=依赖方,列=被依赖方(● 编译期依赖,○ 仅运行时 HTTP/协议交互,空白=无关系):
| 依赖方 ↓ / 被依赖 → | common | base | server | admin | image | schedule | demo | test |
|---|---|---|---|---|---|---|---|---|
captcha-common | — | |||||||
captcha-base | ● | — | ||||||
captcha-server | ● | ● | — | ○(禁) | ○ | |||
captcha-admin | (传递) | ● | ○(禁) | — | ||||
captcha-image | ○(被调) | — | ||||||
captcha-schedule | (传递) | ● | ○ | — | ||||
captcha-demo | ○ | ○ | — | |||||
captcha-test | ○ | ○ | — |
关键结论:
server与admin之间零编译依赖——两者靠共享库 + MySQL/Redis 通信,"配置生效"是跨进程语义,不存在方法级调用。违反这条会造成双端编译耦合与部署顺序依赖。image完全独立,不与base/common发生任何编译关系(上表空白),这是全仓唯一的「可独立升级」模块。demo/test是纯外部消费者,只通过 HTTP 协议交互,因此协议字段(captchaId、data、validate)改动会直接打断它们。base是唯一被所有服务端模块依赖的模块,改base必须全量重建(server/admin/image 之外的 4 个模块 + schedule)。
4. 各模块包结构与职责
| 模块 | 主要包 | 职责 |
|---|---|---|
captcha-common | common/constant(错误码/命中码/CaptchaAction/InterfaceVersion)、common/model(CaptchaInfo)、common/utils(NIS/Servlet/加解密)、common/fingerprint | 契约与算法工具 |
captcha-base | dao(MyBatis Mapper)、redis/*Service、service/impl(CaptchaInfoServiceImpl 写 ES)、sys/conf(SysConfLoader 30s 轮询)、license/(nc_license)、licence/(nc_licence)、imagemeta/*(题面生成)、upload/(S3UploadService)、email、prometheus、version(VersionCacheService、VersionProductCacheService)、filter(DecryptingPropertyPlaceholderConfigurer、PropertiesEncryptUtil)、metrics、fsm/space/icon/clickconf 缓存、utils | 基础设施与唯一数据出口 |
captcha-server | api(*ControllerV2、GetConfController、SdkCollectController、api/sms、api/entity)、abst(AbstractController、ApiResponse、ErrorCode、AbstractCacheService)、security/interceptor(InterceptorService、InterceptorServiceFactory、Ip*/UserListInterceptorServiceImpl)、common/interceptor(LicenceCheckInterceptor)、limiter(RedisRateLimiter)、prometheus、service(GetCaptchaService/CheckCaptchaService/VerifyCaptchaService)、service/{puzzle,click,icon,drag,avoid,space,wordgroup,wordorder,voice,sms}(各含 get/check/provider 三件套)、service/rules/rule/validates(14 个 Validator)、service/rules/frequency(高频统计)、service/rules/model(+cache)、service/rules/switchover、service/request(统计 Handler)、service/list(名单同步)、service/ip、service/smart、service/meta | 协议入口与风控判定 |
captcha-admin | app、product、namelist、freq、image、captchainfo、stat(+excel)、version、model、audit(annotation/aspect)、sys(用户/角色/权限/SysConfController/SafeConfigController)、licence(旧表)、license(新表)、security(+security/captcha)、sso、interceptor(SafeCheckInterceptor、IpBlackInterceptor、SpecialCharCheckFilter)、filter(PrefixRedirectFilter)、fm、util/utils、job、config、dash、abst | 运营配置与后台安全 |
captcha-image | api(UploadController)、service(ImageProcessService)、utils(TokenUtils)、filter | 图片落盘与分发 |
captcha-schedule | abst(AbstractWorker/AbstractProduceWorker/AbstractFillTask/AbstractCreateService)、produce/worker(+task)、produce/create(8 个 *CreateService)、produce/cache(ElementCacheService)、job/clean、job(SubVersionSwitchJob)、stat、lsh/feature、service(WarmupService)、configurations | 离线生产与清理 |
captcha-demo | sdk/java(NECaptcha、NECaptchaVerifier)、页面资源 | 接入示例 |
captcha-test | auto(Main、base、crack 系列) | 自动化破解基线 |
5. 各 WAR 的 web.xml 与 Spring 配置
5.1 captcha-server
web.xml:metadata-complete="true"+<absolute-ordering/>关闭 Tomcat 注解扫描(captcha-server/src/main/webapp/WEB-INF/web.xml:6-8);根上下文classpath:server-application-context.xml(:28-34);DispatcherServlet映射/(:41-49);额外注册io.prometheus.client.exporter.MetricsServlet到/metrics(:51-58);5 个错误码页 +Throwable兜底(:65-89)。server-application-context.xml:仅 importbase-application-context.xml+ 扫描com.netease.is.nc.server的@Component(排除@Controller)+ 声明switchOverServicebean(captcha-server/src/main/resources/server-application-context.xml:17-24)。captcha-server-servlet.xml:mvc:interceptors两条 + FreeMarker 视图 + 扫@Controller+mvc:annotation-driven+ 静态资源/**→/static/+mvc:cors全开(captcha-server/src/main/webapp/WEB-INF/captcha-server-servlet.xml:21-76)。
5.2 captcha-admin
web.xml:Filter 链顺序是硬约束——CharacterEncodingFilter→specialCharCheckFilter(DelegatingFilterProxy,仅在sys_conf的admin.special.char.check.conf声明的接口上生效)→prefixRedirectFilter(X-Forwarded-Prefix补齐网关前缀,必须早于安全过滤器)→springSecurityFilterChain(captcha-admin/src/main/webapp/WEB-INF/web.xml:10-55)。- Servlet 映射用后缀而非
/:*.html、*.json、/sso/*、/sso/consume、/sso/mock/*,以及 4 个/health/*(:75-90)。session-timeout=3600+tracking-mode=COOKIE(:92-95)。 - Spring 配置两份:根
application-context.xml(Service/DAO 层)+ Servletspringmvc.xml(Controller 层)+spring-security.xml(认证授权)。
5.3 captcha-image
web.xml:仅CharacterEncodingFilter;根上下文classpath:application-context-image.xml;DispatcherServlet映射/*(captcha-image/src/main/webapp/WEB-INF/web.xml:8-41)。- 无 Spring Security、无拦截器——上传鉴权靠业务层
UploadController+TokenUtils校验 token,而不是容器过滤器。
6. 拦截器注册一览
本系统存在两套完全不同的拦截器机制,混用会造成"以为拦了其实没拦"。
| 机制 | 注册位置 | 生效范围 | 成员 |
|---|---|---|---|
Spring MVC HandlerInterceptor(容器级) | captcha-server-servlet.xml:21-33 | /api/** | LicenceCheckInterceptor、PrometheusMetricsInterceptor |
业务责任链 InterceptorService(方法级) | InterceptorServiceFactory 构造注入 List<InterceptorService>(captcha-server/src/main/java/com/netease/is/nc/server/security/interceptor/InterceptorServiceFactory.java:41) | CheckControllerV2 内部显式调用 | IpListInterceptorServiceImpl(IP 黑/白名单)、UserListInterceptorServiceImpl(用户黑名单)、指纹拦截 |
| 后台安全链 | spring-security.xml + SafeCheckInterceptor + IpBlackInterceptor | admin 全部 *.html/*.json | 认证、URL 级授权、无操作退出、登录失败锁定 |
因此「名单拦截」不在 /api/** 的 MVC 拦截器里,而是在 check 业务内部按「指纹 → IP → 用户」短路执行;/api/v3/get、/api/v2/getconf 不经过名单链,只有 License 与指标拦截。这是排查"为什么某 IP 还能取图"的关键前提。
7. 构建顺序与 -pl ... -am 用法
Maven reactor 会按依赖关系自动排序,固定为:captcha-common → captcha-base → captcha-server / captcha-admin / captcha-schedule → captcha-image / captcha-demo / captcha-test。
bash
# 全量构建(跳过测试)
mvn clean package -DskipTests
# 安装到本地仓库(跨模块联调必需,否则 -pl 单模块会找不到 SNAPSHOT)
mvn clean install -DskipTests
# 只构建 server 及其全部上游依赖(-am = also make)
mvn clean package -pl captcha-server -am -DskipTests
# 只构建 admin(改后台页面时的最短路径)
mvn clean package -pl captcha-admin -am -DskipTests
# 只重建 base 之后的下游(改 base 的推荐做法:先 install 再逐个打)
mvn clean install -pl captcha-base -am -DskipTests
# 跑单个测试类
mvn test -pl captcha-test -Dtest=ClassName
# 批量改版本号(注意 captcha-demo 自带 version,需要额外处理)
mvn versions:set -DnewVersion=1.3.0-SNAPSHOT -DprocessAllModules=true -DallowSnapshots=true四个实操要点:
-pl X -am只会构建 X 的上游;要连带下游一起动,得写多个-pl或用-amd。captcha-server编译依赖target/generated-sources里的 Scala 产物,因此必须走 Maven 全流程;直接javac会失败(captcha-server/pom.xml:342-348)。captcha-schedule的copy-dependencies绑定在package阶段,只跑compile不会产出target/lib,Docker 构建会因缺文件失败。captcha-demo的1.7编译器配置 + 独立version,在统一升级 JDK 或版本时是最先报错、也最容易被忽略的模块。