# api-registry-spring-boot-starter **Repository Path**: library-components/api-registry-spring-boot-starter ## Basic Information - **Project Name**: api-registry-spring-boot-starter - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-07 - **Last Updated**: 2025-09-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # api-registry-spring-boot-starter > **零持久层耦合的 API 扫描与导出 Starter(Spring Boot 2.7.x / Servlet MVC)** > 自动扫描 **所有 Spring MVC 路由**(是否标注 `@ApiResource` 均可),生成统一的 API 清单(模块、权限码、访问级别、匹配策略、环境掩码、控制器/包等),并通过 **事件** 或 **SPI 回调** 交给你的业务系统“落库/下发”。Starter 本身**不写数据库**、**不依赖 MyBatis/JPA**。 --- ## ✨ 功能特性 * **一次性全量扫描**:基于 `RequestMappingHandlerMapping`,枚举所有 `@RequestMapping`(含 `@GetMapping`/`@PostMapping` …)。 * **支持“无 `@ApiResource` 注解”**:未标注注解时使用合理默认值(见下文“默认值与回退”)。 * **多 HandlerMapping 兼容**:优先 beanName=`requestMappingHandlerMapping`;若不存在则**合并**所有 `RequestMappingHandlerMapping`。 * **PathPattern/Ant 双栈**:兼容 Spring 5.3+ 的 `PathPatternsRequestCondition` 与旧版 `PatternsRequestCondition`。 * **权限码自动生成**:`code` 为空时按 `METHOD:path` 生成稳定权限码(小写,无空白)。 * **匹配策略智能升级**:`matchStrategy=EXACT` 且 `path` 含 `*`/`?` 时可自动升级为 `ANT`(可配)。 * **环境位掩码**:`(注解.envMask | 0→ALL) & (全局.defaultEnvMask | 0→ALL)`。 * **模块名解析更强**:未标注时也可根据 **URL/包前缀规则**、**Controller 所在包**、**应用/构建信息** 自动确定模块名(详见“模块名优先级”)。 * **白名单严控**:仅在 `ANON` / `LOGIN_SKIP_AUTH` 才产出白名单;`AUTH` 永不进入白名单。 * **多种导出方式**:直接注入 `ApiScanService`、监听 `ApiScannedEvent`、实现 `ApiRegistryExporter`,可选 **Actuator 端点** / **REST 端点**。 --- ## 🧱 兼容性 * **JDK**:1.8+ * **Spring Boot**:2.7.x * **Web 框架**:Servlet MVC(**不**包含 WebFlux) > WebFlux / Boot 3 可用同样接口自实现扫描器(或等扩展版)。 --- ## 📦 安装 **Maven** ```xml io.apiregistry api-registry-spring-boot-starter 1.0.0 ``` **Gradle (Kotlin)** ```kotlin implementation("io.apiregistry:api-registry-spring-boot-starter:1.0.0") ``` > 通过 `spring.factories` 自动装配,无需额外 `@Import`。 --- ## 🚀 快速开始 ### 1) 可选:在 Controller 上标注 `@ApiResource` ```java @RestController @RequestMapping("/users") public class UserController { @GetMapping("/{id}") @ApiResource( name = "查询用户详情", module = "user", access = WhiteListTypeEnum.AUTH, // 登录 + 鉴权(默认) matchStrategy = MatchStrategyEnum.EXACT, // 默认 EXACT envMask = 15 // ALL ) public UserDTO detail(@PathVariable Long id) { ... } @PostMapping("/login") @ApiResource( name = "用户登录", module = "auth", access = WhiteListTypeEnum.ANON // 匿名白名单 ) public TokenDTO login(@RequestBody LoginCmd cmd) { ... } } ``` > 也可以**完全不标注** `@ApiResource`:Starter 仍会收录该接口,按默认规则生成清单(见下文“默认值与回退”)。 ### 2) 选择一种导出方式 **A. 直接拉取** ```java @Component @RequiredArgsConstructor public class ApiExportRunner implements ApplicationRunner { private final ApiScanService apiScanService; @Override public void run(ApplicationArguments args) { ApiScanResult result = apiScanService.scan(); // TODO: 落库 / 下发 } } ``` **B. 监听事件**(容器就绪后自动扫描 → 发布 `ApiScannedEvent`) ```java @Component public class ApiRegistryEventListener { @EventListener public void onScanned(ApiScannedEvent event) { ApiScanResult result = event.getResult(); // TODO: 落库 / 下发 } } ``` **C. 实现 SPI** ```java @Component public class MyApiExporter implements ApiRegistryExporter { @Override public void export(ApiScanResult result) { // TODO: 落库 / 下发 } } ``` > 当 `api.registry.auto-scan-on-startup=true`(默认)时,容器就绪后会执行一次全量扫描并**发布事件** + **回调 SPI**。 ### 3)(可选)开启导出端点 **Actuator 端点** ```yaml management: endpoints: web: exposure: include: "api-registry" ``` 访问:`GET /actuator/api-registry` **REST 导出端点** ```yaml api: registry: web-export-enabled: true ``` 访问:`GET /internal/api-registry/export`(建议仅内网并加鉴权/网关限制) --- ## 🔧 默认值与回退(无 `@ApiResource` 时) 当方法/类**未**标注 `@ApiResource` 时: * **name**:默认回落到 `code` * **code**:按 `METHOD:path` 生成(统一小写、去空白),如 `GET:/rs/ping` → `get:/rs/ping` * **access**:默认 `LOGIN_SKIP_AUTH`(可通过 `api.registry.default-access-for-unannotated` 覆盖) * **ruleType**:仅当 `access` 为 `ANON` 或 `LOGIN_SKIP_AUTH` 时输出;默认 `PATH` * **matchStrategy**:默认 `EXACT`;若 `path` 含 `*` / `?` 且 `auto-ant-when-wildcard=true`,自动升级为 `ANT` * **envMask**:与全局 `default-env-mask` 按位 AND(0 视为 ALL) * **module**:按**模块名优先级**解析(见下文),即使无注解也会用 URL/包/应用信息推导 --- ## 🧠 模块名优先级(从高到低) 1. **`@ApiResource.module`**(方法或类上任意一处有即可) 2. **`api.registry.package-modules` 规则** * **URL 前缀**:`prefix` 以 `/` 开头(`/rs`、`/admin/**`),**最长前缀优先** * **包前缀**:`prefix` 非 `/` 开头(`io.stall.web.rs`),**最长前缀优先** 3. **Controller 所在包名**(例如 `io.stall.web.rs`) 4. **应用名称** * 首选 `application.name`,其次 `spring.application.name` 5. **构建信息 `pom `**(需要启用 `build-info`:`BuildProperties#getName()`) 6. **兜底**:`"general"` > Starter **不会**把 `api-registry-spring-boot-starter` 的 **artifactId** 当模块名(避免误命中)。 **配置示例**(包/URL 规则二选一或同时使用): ```yaml api: registry: package-modules: - prefix: io.stall.web.rs module: core - prefix: /rs module: core ``` --- ## ⚙️ 配置项总览 ### 1) `api.registry.*`(`ApiRegistryProperties`) | Key | 类型 | 默认值 | 说明 | | -------------------------------- | ------------------------- | ----------------: | ------------------------------------------------------------------ | | `auto-scan-on-startup` | `boolean` | `true` | 容器就绪后自动全量扫描并发布事件/回调 SPI | | `web-export-enabled` | `boolean` | `false` | 开启 REST 导出端点 `/internal/api-registry/export` | | `default-access-for-unannotated` | `WhiteListTypeEnum` | `LOGIN_SKIP_AUTH` | **无注解**接口的默认访问级别(`ANON` / `LOGIN_SKIP_AUTH` / `AUTH`) | | `default-env-mask` | `int` | `15`(ALL) | 与注解 `envMask` 按位与(0 视为 ALL) | | `auto-ant-when-wildcard` | `boolean` | `true` | `EXACT` 且 `path` 含 `*`/`?` 时自动升级为 `ANT` | | `include-base-packages` | `Set` | `[]` | **包前缀白名单**:只扫描这些包下 Controller(仅在**手动调用**或**自定义启动流程**时用;内置自动扫描默认全量) | | `package-modules` | `List` | `[]` | **包/URL 前缀 → 模块名** 规则,**最长前缀优先** | | `dev-consistency-check-enabled` | `boolean` | `false` | 开发期轻量校验切面,仅日志提示 | **`PackageModuleRule` 字段** * `prefix`:`/` 开头表示 URL 前缀(如 `/rs`),否则为**包前缀**(如 `io.stall.web.rs`) * `module`:命中的模块名 **示例:按包过滤 + 规则映射** ```yaml api: registry: include-base-packages: - io.stall.web # 扫描该前缀下所有 Controller package-modules: - prefix: io.stall.web.core module: core - prefix: /admin module: admin ``` > **注意**:若你把范围限制成 `io.stall.web.core`、`io.stall.web.mall`、`io.stall.web.bff`,那么位于 `io.stall.web.rs` 的控制器将**不会**被扫描。请把上层前缀(如 `io.stall.web`)或具体包(`io.stall.web.rs`)加入白名单。 ### 2) 模块命名与推导(可选) 若你使用了 `ModuleNameSupport`(默认内置),它只在**优先级 5/6 之前**作为“类/包名推导”的补充能力(不会覆盖更高优先级)。可通过自定义 `ModuleNameSupport` Bean 调整命名风格(kebab/snake/camel)、包尾层数截取、代理类名清理等。 > **最佳实践**:优先用 `package-modules` 明确模块归属;`ModuleNameSupport` 作为最后的美化/兜底。 --- ## 🧪 数据结构 ### `ApiDescriptor`(核心字段) ```json { "name": "查询用户详情", "code": "get:/users/{id}", "module": "user", "path": "/users/{id}", "method": "GET", "access": "AUTH", "ruleType": null, "matchStrategy": "EXACT", "envMask": 15, "controllerClass": "com.example.UserController", "controllerPackage": "com.example", "handlerMethodName": "detail" } ``` ### `ApiScanResult` ```json { "apis": [ /* ApiDescriptor... */ ], "modules": ["user","auth","order"], "scannedAt": "2025-09-07T03:00:00Z" } ``` --- ## 🧭 典型“落库/下发”流程(示例) 1. 启动后,`ApiScanService` 产出 `ApiScanResult`。 2. 通过事件/导出器把结果传递给你的业务层。 3. 业务层完成: * 新增/更新 API 描述; * 同步白名单(仅 `ANON` / `LOGIN_SKIP_AUTH`); * 生成/更新权限码与模块聚类; * 记录 `envMask` / 匹配策略等。 4. 可对比历史清单,标记下线/告警。 > Starter 不涉及数据库与事务,由你完全掌控。 --- ## 🔐 权限/白名单/匹配策略/环境位 * **访问级别 (`WhiteListTypeEnum`)** * `AUTH`:登录+鉴权(**不**生成白名单) * `LOGIN_SKIP_AUTH`:登录跳过鉴权(生成白名单) * `ANON`:匿名访问(生成白名单) * **白名单规则类型 (`WhitelistRuleTypeEnum`)** * `PATH`(默认)、`ANON`、`LOGIN_SKIP_AUTH`(实际落表时通常只需要区分 ANON / LOGIN_SKIP_AUTH) * **匹配策略 (`MatchStrategyEnum`)** * `EXACT` | `ANT`(支持通配符 `*`/`?`) * **环境位** * 典型定义:`DEV=1, TEST=2, STAGING=4, PROD=8, ALL=15` --- ## 🧰 高级用法 ### 1) 手动按包过滤扫描 ```java Set include = new LinkedHashSet<>(); include.add("io.stall.web"); ApiScanResult result = apiScanService.scan(include); ``` > 若过滤后**结果为 0**,Starter 会自动**回退到全量扫描**一次,避免配置误杀。但如果其他包仍有结果(非 0),却遗漏了你想要的包,请正确调整 `include-base-packages`。 ### 2) 启动时就按包过滤(覆盖内置 Bootstrap) 内置自动扫描默认全量。若要**启动即按包过滤**,可自定义一个同名 `apiRegistryBootstrap` Bean(SmartLifecycle),在其中使用 `props.getIncludeBasePackages()` 调用 `scan(include)`(样例略)。 ### 3) 按 URL 归类模块 ```yaml api: registry: package-modules: - prefix: /rs module: core # /rs/** 归 core ``` --- ## 🧷 故障排查(FAQ) **Q1. 启动后扫描结果为空?** * 确保触发时机在 **容器就绪** 后(Starter 内置 SmartLifecycle,已处理); * Controller 确实注册到了 MVC(而非 WebFlux); * 未被 `include-base-packages` 排除; * 打开日志:`logging.level.io.apiregistry=DEBUG`,可看到每个 `HandlerMethod` 的 `controllerClass/pkg/paths/methods`。 **Q2. 某些接口扫不到?** * 检查是否限制了包白名单(如遗漏 `io.stall.web.rs`); * 若限制了多个包但结果非 0,**不会触发“全量回退”**,请补齐包前缀; * 确认是否注册到了主 `DispatcherServlet` 的 `RequestMappingHandlerMapping`。 **Q3. 模块名不符合预期?** * 检查优先级链条:注解 `module` → `package-modules`(URL/包前缀)→ **Controller 包名** → 应用名 → pom `` → `general`; * 直接用 `package-modules` 明确绑定是最简洁稳妥的方式。 **Q4. 端点 404?** * Actuator 需要显式暴露;REST 端点需要开启 `web-export-enabled`,且建议只在内网使用并加鉴权。 --- ## 🧮 性能与时序 * 扫描在容器就绪后执行(`SmartLifecycle`,`phase=Integer.MAX_VALUE`),确保路由均已注册。 * 扫描仅遍历已注册的 `HandlerMethod`,开销与 Controller 数量线性相关,通常在(十)毫秒级到百毫秒级。 * 去重以 `code` 为键;同一 `code` 多处声明时,`envMask` **OR 合并**,其他字段以首次出现为准(建议保持唯一)。 --- ## 🔒 安全提示 * 默认**不**暴露 REST 导出端点;若开启,请配合 **网关 / IP 白名单 / 鉴权**。 * 白名单**仅**来源于 `ANON`/`LOGIN_SKIP_AUTH`;`AUTH` 永不放入白名单。 --- ## 🧪 构建与发布 * 构建:`mvn -U -T 1C -DskipTests clean install` * 自动装配入口(已内置): ``` org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ io.apiregistry.autoconfig.ApiRegistryAutoConfiguration ``` --- ## 📝 许可证 MIT(可按组织策略替换) --- ## 📬 反馈 需要 **WebFlux**、**差异比对**、**多应用聚合扫描** 等能力?欢迎在你的主工程提出需求或 PR。