# quick-start-boot-framwork **Repository Path**: roggie/quick-start-boot-framwork ## Basic Information - **Project Name**: quick-start-boot-framwork - **Description**: 一个面向 Spring Boot 的基础设施能力框架,提供可靠消息、MQTT 接入、Netty 协议服务端、统一缓存、统一 JSON、可观测性和敏感字段脱敏等通用模块。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-25 - **Last Updated**: 2026-07-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # quick-start-boot-framwork ## 项目简介 这是一个基于 Maven 的多模块快速启动框架仓库,当前主要模块包括: - `quick-start-boot-core` - `quick-start-boot-codegen-annotations` - `quick-start-boot-codegen-processor` - `quick-start-boot-masking` - `quick-start-boot-json` - `quick-start-boot-mqtt` - `quick-start-boot-acl` - `quick-start-boot-spring-netty` - `quick-start-boot-cache` - `quick-start-boot-sample-order` ## 仓库统一依赖规范 当前仓库的 Maven 依赖分层约定统一按下面这套规则执行: ### 1. `boot-parent` 只负责版本和编译配置 - `boot-parent` 负责统一: - 依赖版本 - `maven-compiler-plugin` - 注解处理器链路 - 通用插件默认配置 - `boot-parent` 中的 `dependencyManagement` 只做“版本约束”,不等于子模块已经自动引入了这些依赖 - 子模块真正是否拥有某个依赖,仍然取决于它自己或它的上游模块有没有在 `` 里声明 ### 2. 通用依赖放在 `quick-start-boot-core` - 仓库级通用依赖统一由 `quick-start-boot-core` 收口 - 目前 `lombok` 就属于这一类通用依赖 - 也就是说: - `boot-parent` 负责给 `lombok` 定版本和注解处理器 - `quick-start-boot-core` 负责真正声明 `lombok` - 其他模块如果已经依赖 `quick-start-boot-core`,就不需要再重复显式声明 `lombok` 这套设计是合理的,原因是: - 版本统一在 parent,避免各模块自己写版本 - 通用依赖统一在 core,避免每个子模块重复声明一遍 - 子模块 `pom.xml` 更干净,也更符合当前仓库“core 兜通用能力”的组织方式 ### 3. 配置处理器是例外,谁定义配置谁声明 像 `spring-boot-configuration-processor` 这类依赖,不属于“通用业务依赖”,而属于“谁定义配置元数据谁负责声明”的范畴。 统一规则是: - 哪个模块里定义了 `@ConfigurationProperties` - 哪个模块就在自己的 `pom.xml` 里显式声明 `spring-boot-configuration-processor` 不要把这类依赖也统一塞进 `quick-start-boot-core`,否则会把模块边界搞混。 ### 4. 编译期代码生成按“注解 API + 处理器实现”分层 仓库内编译期代码生成统一由 `quick-start-boot-codegen-*` 模块承载: - `quick-start-boot-codegen-annotations` - 只放对业务源码可见的 SOURCE 注解 - 业务模块可以作为普通依赖声明 - `quick-start-boot-codegen-processor` - 只放 annotation processor 实现 - 只能由需要生成代码的模块在 `maven-compiler-plugin.annotationProcessorPaths` 中显式启用 不要把 codegen processor 放进 `quick-start-boot-core`,也不要放进父 POM 的全局注解处理器链路。这样可以避免编译期工具污染普通依赖边界,也能让“谁需要生成代码谁声明”这件事足够清晰。 当前 codegen 注解均使用“标记类触发生成”的方式,业务模型无需直接依赖编译期处理器: ```java import org.dw.quickstart.boot.codegen.annotation.AutoConvert; import org.dw.quickstart.boot.codegen.annotation.DeepCloneConvert; import org.dw.quickstart.boot.codegen.annotation.EntityRef; import org.dw.quickstart.boot.codegen.annotation.GenerateProtobufPayloadExtractor; @AutoConvert(source = UserDto.class, target = UserDo.class) @DeepCloneConvert(UserDto.class) @EntityRef(User.class) @GenerateProtobufPayloadExtractor(MessageProto.MessageEnvelope.class) final class CodegenMarker { } ``` 生成规则统一如下: - `@AutoConvert` 生成 `.generated.Mapper` - `@DeepCloneConvert` 生成 `.generated.DeepCloneMapper` - `@EntityRef` 生成 `.generated.Ref` - `@GenerateProtobufPayloadExtractor` 生成 `.generated.PayloadExtractor` 业务模块接入时,只把注解 API 放进普通依赖;处理器只放进当前模块自己的 `maven-compiler-plugin.annotationProcessorPaths`: ```xml org.dw quick-start-boot-codegen-annotations org.apache.maven.plugins maven-compiler-plugin org.dw quick-start-boot-codegen-processor ${quick-start-boot-codegen-processor.version} ``` 使用 `@AutoConvert` 或 `@DeepCloneConvert` 的模块,还需要把 `org.mapstruct:mapstruct` 作为普通编译依赖声明;codegen processor 仍然只放在 `annotationProcessorPaths` 中。 ### 5. JSON 统一依赖路径 仓库内 JSON 能力统一由 `quick-start-boot-json` 模块族提供。 面向使用方的统一入口固定是: - `quick-start-boot-json-starter` 统一约定如下: - 业务应用、业务 starter、自动装配模块 - 优先依赖 `quick-start-boot-json-starter` - 这样可以同时拿到 `JsonOperations`、统一 `ObjectMapper` 和自动装配 - 纯接口契约模块 - 只依赖 `quick-start-boot-json-api` - 只暴露接口,不引入具体实现 - 只在测试或底层实现里需要 `DefaultJsonOperations` - 才依赖 `quick-start-boot-json-core` 需要单独说明的例外: - 如果某个模块只是提供 Jackson 注解、序列化器、反序列化器这类 SPI 扩展 - 它本身并不负责提供统一 `ObjectMapper` 或 `JsonOperations` - 那么可以直接依赖 Jackson 基础库,而不是强行依赖 `quick-start-boot-json-starter` 例如当前仓库里的 `quick-start-boot-masking-jackson` 就属于这一类:它提供数据掩码注解和 Jackson 扩展,不承担统一 JSON 运行时入口职责。 不要在业务模块里直接 new `ObjectMapper`,也不要绕开统一入口自己拼一套 JSON 自动装配。 ## 数据掩码模块 ### 最小可运行示例 业务模块统一依赖启动器: ```xml org.dw quick-start-boot-masking-starter ``` 在字符串属性上通过类型安全的注解声明策略: ```java public record UserView( @Mask(strategy = MobilePhoneMaskingStrategy.class) String mobilePhone, @Mask(strategy = EmailMaskingStrategy.class) String email) { } ``` 启动器会把数据掩码模块注册到仓库统一的 `ObjectMapper`。属性值为 `null` 时保持 `null`;策略、决策器配置错误或执行失败时直接抛出异常,不会降级输出原始值。 ### 注解参数 - `strategy`:必填,指定实现 `MaskingStrategy` 的掩码策略类型。 - `policy`:选填,指定实现 `MaskingPolicy` 的决策器类型,默认使用 `AlwaysMaskingPolicy`。 - 注解仅允许用于字符串字段、访问方法或记录组件。 ### 内置策略 模块内置身份证号码、手机号码、地址、电子邮箱、银行卡号、中文姓名、固定电话号码、用户标识、密码、互联网协议地址、机动车号牌、首字符保留、空字符串和空值等策略。使用方直接在 `@Mask` 中引用对应策略类型,不使用字符串策略名。 ### 自定义策略和决策器 自定义策略与决策器均作为 Spring Bean 注册,自动配置会在应用启动时收集并冻结组件索引: ```java @Bean CurrentUserMaskingPolicy currentUserMaskingPolicy() { return new CurrentUserMaskingPolicy(); } ``` ```java @Mask( strategy = ChineseNameMaskingStrategy.class, policy = CurrentUserMaskingPolicy.class ) private String name; ``` `MaskingPolicy` 可自行读取当前业务上下文;数据掩码核心模块不认识角色、权限或具体认证框架。本次重构不提供兼容层。 ### 6. 当前仓库 JSON 依赖路径核对结论 这次也顺手把整个仓库里引用 `quick-start-boot-json-*` 的模块扫了一遍,当前结论是统一的,主要分成三类: - 业务 starter / 自动装配入口走 `quick-start-boot-json-starter` - 例如: - `quick-start-boot-cache-starter` - `spring-boot-starter-netty` - `quick-start-boot-reliable-message` 各 producer / consumer starter - 纯契约模块走 `quick-start-boot-json-api` - 例如: - `quick-start-boot-reliable-message-core` - `spring-boot-starter-netty-api` - `spring-boot-starter-netty-common` - 底层实现或测试辅助按需走 `quick-start-boot-json-core` - 例如: 也就是说,当前仓库里没有发现“业务 starter 绕开 `quick-start-boot-json-starter`,自己重新拼一套 JSON 运行时”的情况。 后续新增模块时,统一按这条规则判断即可: - 只要模块要对外提供统一 JSON 运行时能力,就走 `quick-start-boot-json-starter` - 只暴露接口契约,就走 `quick-start-boot-json-api` - 只在实现层或测试层需要底层实现,再按需走 `quick-start-boot-json-core` ## MQTT 模块 `quick-start-boot-mqtt` 已统一为单核心、多适配器结构,当前定位是: - `quick-start-boot-mqtt-api` 与 `quick-start-boot-mqtt-core` 作为唯一公共契约与核心能力 - 业务侧统一通过 `BusinessProtocolAdapter`、`ProtocolMessageHandler`、`ProtocolGateway` 扩展 - 底层传输通过适配器模块接入,Spring Integration 不再作为独立框架产品存在 - 框架统一提供配置校验、异常模型、默认拦截器、统一失败链路与生命周期管理 ### 传输适配模块 - `quick-start-boot-mqtt-transport-paho-v3` - `quick-start-boot-mqtt-transport-paho-v5` - `quick-start-boot-mqtt-transport-spring-integration-v3` - `quick-start-boot-mqtt-transport-spring-integration-v5` ### 依赖方式 同一应用在同一协议版本下只能引入一个传输实现,请勿同时引入 Paho 与 Spring Integration 的同版本模块。 使用 Paho MQTT v3: ```xml org.dw quick-start-boot-mqtt-transport-paho-v3 ${project.version} ``` 使用 Paho MQTT v5: ```xml org.dw quick-start-boot-mqtt-transport-paho-v5 ${project.version} ``` 使用 Spring Integration MQTT v3: ```xml org.dw quick-start-boot-mqtt-transport-spring-integration-v3 ${project.version} ``` 使用 Spring Integration MQTT v5: ```xml org.dw quick-start-boot-mqtt-transport-spring-integration-v5 ${project.version} ``` ### 配置结构 统一使用 `spring.mqtt` 前缀: ```yaml spring: mqtt: connection: protocol-version: v3 uris: - tcp://localhost:1883 username: demo password: demo clean-session: true automatic-reconnect: true connection-timeout: 30 keep-alive-interval: 60 max-reconnect-delay: 10000 inbound: client-id: demo-inbound auto-startup: true subscriptions: - topic: device/+/up qos: 1 allowed-topic-patterns: - device/+/up max-payload-size: 1048576 max-messages-per-second: 1000 outbound: client-id: demo-outbound default-topic: device/reply default-qos: 1 retained: false protocol: strict-matcher-validation: true ``` ### 协议接入示例 定义业务协议适配器: ```java package demo.mqtt; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import org.dw.mqtt.api.model.MqttInboundEnvelope; import org.dw.mqtt.api.model.MqttOutboundEnvelope; import org.dw.mqtt.api.model.ProtocolSendRequest; import org.dw.mqtt.api.spi.BusinessProtocolAdapter; import org.springframework.stereotype.Component; @Component public class DeviceUpProtocolAdapter implements BusinessProtocolAdapter { @Override public String protocolId() { return "device-up"; } @Override public Class payloadType() { return DeviceUpMessage.class; } @Override public List inboundTopicPatterns() { return List.of("device/+/up"); } @Override public boolean supports(MqttInboundEnvelope envelope) { return envelope.topic().startsWith("device/") && envelope.topic().endsWith("/up"); } @Override public DeviceUpMessage decode(MqttInboundEnvelope envelope) { String body = new String(envelope.payload(), StandardCharsets.UTF_8); return new DeviceUpMessage(envelope.topic(), body); } @Override public MqttOutboundEnvelope encode(ProtocolSendRequest request) { return new MqttOutboundEnvelope( "device/reply", request.payload().body().getBytes(StandardCharsets.UTF_8), 1, false, Map.of("protocolId", request.protocolId())); } } ``` 定义业务处理器: ```java package demo.mqtt; import org.dw.mqtt.api.model.MqttInboundEnvelope; import org.dw.mqtt.api.spi.ProtocolMessageHandler; import org.springframework.stereotype.Component; @Component public class DeviceUpMessageHandler implements ProtocolMessageHandler { @Override public Class payloadType() { return DeviceUpMessage.class; } @Override public void handle(DeviceUpMessage payload, MqttInboundEnvelope envelope) { System.out.println("收到设备上报:" + payload.body()); } } ``` 统一发送业务消息: ```java package demo.mqtt; import org.dw.mqtt.api.gateway.ProtocolGateway; import org.dw.mqtt.api.model.ProtocolSendRequest; import org.springframework.stereotype.Service; @Service public class DeviceMessagePublisher { private final ProtocolGateway protocolGateway; public DeviceMessagePublisher(ProtocolGateway protocolGateway) { this.protocolGateway = protocolGateway; } public void publish(String body) { protocolGateway.send(ProtocolSendRequest.of( "device-up", new DeviceUpMessage("device/reply", body))); } } ``` 当业务确实需要直接操作原始 topic/qos/retain 时,可以注入 `MqttTransportClient`。 ### 默认能力与异常模型 默认启用以下全局入站能力: - `LoggingInterceptor`:记录基础接收日志 - `PayloadValidationInterceptor`:校验主题白名单与最大载荷 - `RateLimitInterceptor`:按配置限制入站吞吐 统一异常基类为 `MqttException`,常见子类包括: - `MqttConfigurationException` - `MqttConnectionException` - `MqttPublishException` - `MqttSubscribeException` - `ProtocolDecodeException` - `ProtocolEncodeException` - `ProtocolRouteNotFoundException` - `BusinessHandlerException` 所有异常统一携带 `errorCode`、`category`、`retryable`、`context`,入站未处理异常会发布 `MqttInboundFailureEvent`。 ## Jackson 模块 `quick-start-boot-json` 当前定位不是“可切换实现的通用 JSON 抽象层”,而是**面向 Jackson 的统一封装层**。 这样定义它的目的有两个: - 第一,让仓库内所有 Jackson 配置、序列化入口、异常语义和多态扩展点有统一收口,不再散落在各模块里各写一套。 - 第二,让业务应用接入时只需要关心统一入口,不需要自己拼装 `ObjectMapper`、时间模块、大整数序列化规则和多态注册逻辑。 当前它采用聚合工程结构,形式与 `quick-start-boot-spring-netty`、`quick-start-boot-cache` 保持一致: - 物理目录:`quick-start-boot-json/api` - 对外 `artifactId`:`quick-start-boot-json-api` - 物理目录:`quick-start-boot-json/core` - 对外 `artifactId`:`quick-start-boot-json-core` - 物理目录:`quick-start-boot-json/autoconfigure` - 对外 `artifactId`:`quick-start-boot-json-autoconfigure` - 物理目录:`quick-start-boot-json/starter` - 对外 `artifactId`:`quick-start-boot-json-starter` - 聚合目录 `quick-start-boot-json` 本身只负责组织子模块,不作为业务应用依赖入口 这几个目录名为什么要收短: - 物理目录短,避免出现 `quick-start-boot-json/quick-start-boot-json-core/...` 这种重复路径 - 对外 `artifactId` 仍然保持完整语义,业务接入和文档说明不会变模糊 - 后续如果继续扩子模块,路径层级也更容易维护 ### 推荐使用方式 业务应用依赖方式如下: - 这里依赖的是 `quick-start-boot-json-starter` - 它会把 Jackson 基础依赖和本项目自己的自动装配一起带进来 - 业务项目通常不需要单独再拼 `api/core/autoconfigure` ```xml org.dw quick-start-boot-json-starter ${project.version} ``` - 业务侧统一注入 `JsonOperations` - 不再使用旧的 `JsonUtils` - 不再在业务代码中重复创建全局 `ObjectMapper` 下面这个示例为什么这样写: - `JsonOperations` 是对业务开放的统一 Jackson 入口 - 业务服务只依赖这个接口,不需要知道底层 `ObjectMapper` 是怎么装配的 - `toJson` 调用走的是项目统一配置,因此会自动继承时间格式、大整数安全序列化等规则 ```java package demo.json; import org.springframework.stereotype.Service; import quick.start.boot.json.api.JsonOperations; @Service public class DemoJsonService { // 统一注入 Jackson 封装入口,而不是在服务里自己 new ObjectMapper private final JsonOperations jsonOperations; public DemoJsonService(JsonOperations jsonOperations) { this.jsonOperations = jsonOperations; } // 执行后会按框架统一规则完成序列化 public String toJson(Object value) { return jsonOperations.toJson(value); } } ``` ### 配置前缀 统一使用 `quick.start.boot.json`: 这段配置里最常用的几个参数含义如下: - `date-format` - 影响传统 `Date` / `Calendar` 类型的输出格式 - `local-date-time-format` - 影响 `LocalDateTime` 的统一输出格式 - `write-dates-as-timestamps` - 控制日期是否按时间戳输出;大多数业务场景建议保持 `false` - `safe-number-to-string` - 控制超出 JavaScript 安全整数范围的 Long / BigInteger 是否转字符串;前后端交互场景建议保持 `true` ```yaml quick: start: boot: json: date-format: yyyy-MM-dd HH:mm:ss local-date-time-format: yyyy-MM-dd HH:mm:ss pretty-print: false write-dates-as-timestamps: false fail-on-unknown-properties: false field-visibility: false safe-number-to-string: true ``` ### 扩展点 - 需要注册 JSON 多态子类型时,声明 `JsonSubtypeRegistrar` Bean - 上层模块不应再直接接管全局 `ObjectMapper` - `spring-boot-starter-netty` 已改为通过公开扩展点注册 `Message` 多态类型 ## 提交前 Java 自动格式化 父 POM 已统一管理 Spring Java Format 版本和格式规则。本仓库通过项目级 pre-commit hook, 只格式化本次已经暂存的 Java 文件,并将格式化结果自动加入当前 commit。 首次克隆后,在仓库根目录执行一次: ```powershell powershell -ExecutionPolicy Bypass -File tools/install-git-hooks.ps1 ``` 也可以直接执行: ```bash git config core.hooksPath .githooks ``` 手动格式化某个 Maven 模块: ```bash mvn -f /pom.xml spring-javaformat:apply ``` 注意事项: - 未执行 git add 的 Java 文件不会参与本次格式化。 - 同一个 Java 文件同时存在已暂存和未暂存修改时,hook 会中止 commit,避免未暂存内容被误提交。 - Java 源码在 source root 下的相对路径包含逗号时,由于 `spring-javaformat.includes` 使用逗号分隔, hook 会安全中止并提示调整路径。 - Maven 或格式化插件执行失败时,hook 会中止 commit,但失败前可能已经修改工作区中的格式化目标; 请先检查格式化结果,确认无误后重新执行 `git add`,再执行 `git commit`。