# 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。