{"id":45893007,"url":"https://github.com/BigGG-Guardian/guardian","last_synced_at":"2026-03-13T06:00:46.610Z","repository":{"id":338546214,"uuid":"1156246434","full_name":"BigGG-Guardian/guardian","owner":"BigGG-Guardian","description":"防重提交、接口限流、接口幂等、参数自动Trim、慢接口检测、请求链路追踪、IP黑白名单、防重放攻击、接口开关 —— 一个 Starter 搞定 API 请求防护。","archived":false,"fork":false,"pushed_at":"2026-03-10T04:08:57.000Z","size":1705,"stargazers_count":105,"open_issues_count":2,"forks_count":21,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-10T12:47:26.171Z","etag":null,"topics":["anti-duplicate","idempotent","interceptor","java","maven-central","redis","repeat-submit","spring-boot","spring-boot-starter"],"latest_commit_sha":null,"homepage":"https://central.sonatype.com/artifact/io.github.biggg-guardian/guardian-repeat-submit-spring-boot-starter","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BigGG-Guardian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-12T12:36:55.000Z","updated_at":"2026-03-10T04:09:00.000Z","dependencies_parsed_at":"2026-02-15T10:09:32.272Z","dependency_job_id":null,"html_url":"https://github.com/BigGG-Guardian/guardian","commit_stats":null,"previous_names":["biggg-guardian/guardian"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/BigGG-Guardian/guardian","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigGG-Guardian%2Fguardian","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigGG-Guardian%2Fguardian/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigGG-Guardian%2Fguardian/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigGG-Guardian%2Fguardian/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BigGG-Guardian","download_url":"https://codeload.github.com/BigGG-Guardian/guardian/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigGG-Guardian%2Fguardian/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30459760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T03:55:51.346Z","status":"ssl_error","status_checked_at":"2026-03-13T03:55:33.055Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anti-duplicate","idempotent","interceptor","java","maven-central","redis","repeat-submit","spring-boot","spring-boot-starter"],"created_at":"2026-02-27T19:00:39.308Z","updated_at":"2026-03-13T06:00:46.598Z","avatar_url":"https://github.com/BigGG-Guardian.png","language":"Java","funding_links":[],"categories":["容错组件"],"sub_categories":["Spring Cloud框架"],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://central.sonatype.com/artifact/io.github.biggg-guardian/guardian-repeat-submit-spring-boot-starter\"\u003e\u003cimg src=\"https://img.shields.io/maven-central/v/io.github.biggg-guardian/guardian-repeat-submit-spring-boot-starter?label=Maven%20Central\u0026color=orange\" alt=\"Maven Central\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Java-1.8+-blue?logo=openjdk\u0026logoColor=white\" alt=\"Java\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Spring%20Boot-2.7.x-6DB33F?logo=springboot\u0026logoColor=white\" alt=\"Spring Boot\"\u003e\n  \u003ca href=\"https://github.com/BigGG-Guardian/guardian/blob/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-Apache%202.0-blue.svg\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/BigGG-Guardian/guardian/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/BigGG-Guardian/guardian?label=Release\u0026color=green\" alt=\"Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/BigGG-Guardian/guardian/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/BigGG-Guardian/guardian?style=flat\u0026logo=github\" alt=\"Stars\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eGuardian\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\u003cb\u003e轻量级 Spring Boot API 请求层防护框架\u003c/b\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e防重提交、接口限流、接口幂等、参数自动Trim、慢接口检测、请求链路追踪、IP黑白名单、防重放攻击、接口开关 —— 一个 Starter 搞定 API 请求防护。\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/BigGG-Guardian/guardian\"\u003eGitHub\u003c/a\u003e ·\n  \u003ca href=\"https://gitee.com/BigGG-Guardian/guardian\"\u003eGitee\u003c/a\u003e ·\n  \u003ca href=\"https://central.sonatype.com/artifact/io.github.biggg-guardian/guardian-repeat-submit-spring-boot-starter\"\u003eMaven Central\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/guardian-mindmap.png\" alt=\"Guardian 功能全景图\" width=\"700\"\u003e\n\u003c/p\u003e\n\n---\n\n## 功能一览\n\n| 功能       | Starter                                      | 注解 | YAML | 说明                                           |\n|----------|----------------------------------------------|------|------|----------------------------------------------|\n| 防重复提交    | `guardian-repeat-submit-spring-boot-starter` | `@RepeatSubmit` | ✅ | 防止用户重复提交表单/请求                                |\n| 接口限流     | `guardian-rate-limit-spring-boot-starter`    | `@RateLimit` | ✅ | 滑动窗口 + 令牌桶，双算法可选                             |\n| 接口幂等     | `guardian-idempotent-spring-boot-starter`    | `@Idempotent` | — | Token 机制保证接口幂等性，支持结果缓存                       |\n| 参数自动Trim | `guardian-auto-trim-spring-boot-starter`     | — | ✅ | 自动去除请求参数首尾空格 + 不可见字符替换                       |\n| 慢接口检测    | `guardian-slow-api-spring-boot-starter`      | `@SlowApiThreshold` | ✅ | 慢接口检测 + Top N 统计 + Actuator 端点               |\n| 请求链路追踪   | `guardian-trace-spring-boot-starter`         | — | ✅ | 自动生成/透传 TraceId，MDC 日志串联，支持跨线程传递、MQ 链路追踪     |\n| IP黑白名单   | `guardian-ip-filter-spring-boot-starter`     | — | ✅ | 全局黑名单 + URL 绑定白名单，支持精确/通配符/CIDR              |\n| 防重放攻击    | `guardian-anti-replay-spring-boot-starter`   | — | ✅ | Timestamp + Nonce 双重校验，nonce TTL 与 timestamp 窗口解耦 |\n| 接口开关     | `guardian-api-switch-spring-boot-starter`    | — | ✅ | 动态关闭/开启接口                                    |\n| 参数签名     | `guardian-sign-spring-boot-starter`          | `@SignVerify` | ✅ | 支持多种签名算法，请求参数签名验证 + 响应结果签名               |\n| 请求加解密   | `guardian-encrypt-spring-boot-starter`       | — | ✅ | 支持 RSA+AES / SM2+SM4 加密，请求解密 + 响应加密             |\n\n每个功能独立模块、独立 Starter，**用哪个引哪个，互不依赖**。所有模块的 YAML 配置均支持**配置中心动态刷新**（Nacos / Apollo 等），无需重启即可生效。\n\n---\n\n## 快速开始\n\n### 所有starter模块引用\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-starter-all\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\u003e\u003csmall\u003e特别说明：`请求链路追踪模块`若开启RabbitMq/Kafka/RocketMq请求链路追踪，需额外引入对应的依赖(`guardian-trace-rabbitmq`/`guardian-trace-kafka`/`guardian-trace-rocketmq`)\u003c/small\u003e\n\n### 防重复提交\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-repeat-submit-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```java\n@PostMapping(\"/submit\")\n@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS, message = \"订单正在处理，请勿重复提交\")\npublic Result submitOrder(@RequestBody OrderDTO order) {\n    return orderService.submit(order);\n}\n```\n\n**SpEL 动态 Key（v1.9.0+）：**\n- 通过 `spEl` 属性使用 SpEL 表达式动态生成防重 Key\n- `#参数名` - 从请求参数或请求体中获取\n- `#body.参数名` - 显式从请求体中获取\n\n```java\n// 仅请求体\n@RepeatSubmit(interval = 10, spEl = \"#orderId\", message = \"请勿重复提交\")\n\n// 仅请求参数\n@GetMapping(\"/test\")\n@RepeatSubmit(interval = 10, spEl = \"#orderId\")\n\n// 组合使用\n@RepeatSubmit(interval = 10, spEl = \"#userId + ':' + #body.orderId\")\n```\n\nYAML 配置：\n```yaml\nguardian:\n  repeat-submit:\n    urls:\n      - pattern: /api/order/spel-body\n        interval: 10\n        key-scope: user\n        sp-el: \"#orderId\"\n      - pattern: /api/order/spel-mix\n        interval: 10\n        key-scope: user\n        sp-el: \"#userId + ':' + #body.orderId\"\n```\n\n### 接口限流\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-rate-limit-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**滑动窗口（默认）：**\n\n```java\n@RateLimit(qps = 1, window = 60, windowUnit = TimeUnit.SECONDS, rateLimitScope = RateLimitKeyScope.IP)\n```\n\n**令牌桶：**\n\n```java\n@RateLimit(qps = 5, capacity = 20, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)\n```\n\n**SpEL 动态 Key（v1.9.0+）：**\n- 通过 `spEl` 属性使用 SpEL 表达式动态生成限流 Key\n- `#参数名` - 从请求参数或请求体中获取\n- `#body.参数名` - 显式从请求体中获取\n\n```java\n// 仅请求体\n@RateLimit(qps = 5, spEl = \"#orderId\", message = \"操作过于频繁\")\n\n// 仅请求参数\n@GetMapping(\"/search\")\n@RateLimit(qps = 5, spEl = \"#keyword\")\n\n// 组合使用\n@RateLimit(qps = 5, spEl = \"#userId + ':' + #body.productId\")\n```\n\nYAML 配置：\n```yaml\nguardian:\n  rate-limit:\n    urls:\n      - pattern: /api/product/spel-body\n        qps: 5\n        rate-limit-scope: global\n        sp-el: \"#productId\"\n      - pattern: /api/order/spel-mix\n        qps: 5\n        rate-limit-scope: global\n        sp-el: \"#userId + ':' + #body.productId\"\n```\n\n或 YAML 批量配置：\n\n```yaml\nguardian:\n  rate-limit:\n    urls:\n      # 滑动窗口\n      - pattern: /api/sms/send\n        qps: 1\n        window: 60\n        window-unit: seconds\n        rate-limit-scope: ip\n      # 令牌桶\n      - pattern: /api/seckill/**\n        qps: 10\n        capacity: 50\n        algorithm: token_bucket\n        rate-limit-scope: global\n```\n\n### 接口幂等\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-idempotent-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**1. 获取 Token：**\n\n```\nGET /guardian/idempotent/token?key=order-submit\n```\n\n**2. 业务接口携带 Token：**\n\n```java\n@Idempotent(\"order-submit\")\n@PostMapping(\"/order/submit\")\npublic Result submitOrder(@RequestBody OrderDTO order) {\n    return orderService.submit(order);\n}\n```\n\n请求头带上 `X-Idempotent-Token: {token}`，首次请求正常处理，重复请求直接拒绝。\n\n### 参数自动Trim\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-auto-trim-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n零配置即可使用，所有请求参数（表单 + JSON Body）自动去除首尾空格。可选配置不可见字符替换：\n\n```yaml\nguardian:\n  auto-trim:\n    exclude-fields:\n      - password\n      - signature\n    character-replacements:\n      - from: \"\\\\u200B\"\n        to: \"\"\n```\n\n### 慢接口检测\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-slow-api-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n零配置即可使用（默认阈值 3000ms），也可通过注解为单个接口自定义阈值：\n\n```java\n@SlowApiThreshold(1000)\n@GetMapping(\"/detail\")\npublic Result getDetail(@RequestParam Long id) {\n    return detailService.query(id);\n}\n```\n\n超过阈值自动打印 WARN 日志，通过 `GET /actuator/guardianSlowApi` 查看 Top N 慢接口排行。\n\n### 请求链路追踪\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-trace-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n零配置即可使用，每个请求自动生成 TraceId 并写入 MDC。只需在 logback pattern 中加入 `%X{traceId}`：\n\n```xml\n\u003cpattern\u003e%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n\u003c/pattern\u003e\n```\n\n上游服务通过请求头 `X-Trace-Id` 传递 TraceId，下游自动复用，实现跨服务链路串联。支持跨线程传递（`TraceUtils.wrap()` / `TraceTaskDecorator`），`@Async`、`CompletableFuture`、手动线程池场景均可保持 traceId 不丢失。支持 MQ 消息链路追踪（RabbitMQ / Kafka / RocketMQ），发送端自动注入 traceId，消费端通过 AOP 自动提取。\n\n### IP黑白名单\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-ip-filter-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```yaml\nguardian:\n  ip-filter:\n    enabled: true\n    # 全局黑名单（命中直接拒绝所有请求）\n    black-list:\n      - 192.168.100.100\n      - 10.0.0.0/8\n    # URL 绑定白名单（仅白名单 IP 可访问指定接口）\n    urls:\n      - pattern: /admin/**\n        white-list:\n          - 127.0.0.1\n          - 192.168.1.*\n```\n\n匹配优先级：**全局黑名单 \u003e URL 绑定白名单 \u003e 放行**。IP 规则支持精确匹配、通配符（`192.168.1.*`）和 CIDR（`10.0.0.0/8`）。\n\n### 防重放攻击\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-anti-replay-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```yaml\nguardian:\n  anti-replay:\n    enabled: true\n    max-age: 60              # timestamp 有效窗口（秒）\n    nonce-ttl: 86400         # nonce 存活时间（秒，默认 24h）\n    urls:\n      - pattern: /api/payment/**\n      - pattern: /api/transfer/**\n```\n\n客户端请求头携带 `X-Timestamp`（毫秒时间戳）和 `X-Nonce`（UUID），服务端校验时间戳有效性 + Nonce 唯一性，双重防护拦截重放请求。\n\n\u003e **关键设计**：`nonce-ttl` 与 `max-age` 解耦（默认 24h vs 60s），防止攻击者在 Nonce 过期后篡改 Timestamp 重放。搭配 `guardian-sign` 签名模块时，`nonce-ttl` 可缩短至与 `max-age` 相同。\n\n### 接口开关\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-api-switch-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```yaml\nguardian:\n  api-switch:\n    enabled: true                        # 总开关（默认 true）\n    response-mode: json                  # exception / json\n    message: \"接口暂时关闭，请稍后再试\"    # 提示信息（支持 i18n Key）\n    log-enabled: true                    # 是否打印拦截日志\n    interceptor-order: 500             # 拦截器排序\n    disabled-urls:                       # 启动时默认关闭的接口\n      - /api-switch/disabled\n```\n\n### 参数签名\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-sign-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**使用注解：**\n\n```java\n// 基本用法\n@SignVerify(algorithm = SignAlgorithm.SHA256)\n@PostMapping(\"/submit\")\npublic Result submit(@RequestBody OrderDTO order) {\n    return orderService.submit(order);\n}\n\n// 自定义错误信息\n@SignVerify(algorithm = SignAlgorithm.HMAC_SHA256, signVerifyMessage = \"签名验证失败，请检查参数\")\n@PostMapping(\"/payment\")\npublic Result payment(@RequestBody PaymentDTO payment) {\n    return paymentService.process(payment);\n}\n```\n\n**配置示例：**\n\n```yaml\nguardian:\n  sign:\n    enabled: true                        # 总开关（默认 true）\n    secret-key: your-secret-key          # 签名密钥\n    result-sign: true                    # 结果签名开关（默认 false）\n    result-advice-order: 100             # 返回值签名 Advice 排序（默认 100）\n    response-mode: json                  # exception / json\n    log-enabled: true                    # 是否打印拦截日志\n    interceptor-order: 4000              # 拦截器排序\n    sign-header: X-Sign                  # 签名请求头名称\n    timestamp-header: X-Sign-Timestamp   # 时间戳请求头名称\n    max-age: 60                          # 时间戳过期时间（秒）\n    max-age-unit: seconds                # 时间戳过期时间单位\n    missing-timestamp-message: \"缺少时间戳\"    # 缺少时间戳提示\n    missing-sign-message: \"缺少参数签名\"      # 缺少签名提示\n    expired-message: \"请求已过期\"            # 请求过期提示\n    urls:                                # 需要签名验证的 URL 规则\n      - pattern: /api/payment/**\n        algorithm: hmac_sha256\n        sign-verify-message: \"签名验证失败\"\n      - pattern: /api/order/**\n        algorithm: md5\n        sign-verify-message: \"订单接口签名验证失败\"\n```\n\n**详细使用示例：**\n\n1. **基础签名验证**\n   - 添加依赖和配置\n   - 在需要验证的接口上添加 `@SignVerify` 注解\n   - 客户端需要按照规则生成签名并在请求头中传递\n\n2. **结果签名**\n   - 设置 `result-sign: true` 开启结果签名\n   - 响应会包含签名信息，客户端可以验证响应的完整性\n   - 可通过 `result-advice-order` 配置执行顺序（推荐：幂等缓存 200 → 签名 100 → 加密 300）\n\n3. **多算法支持**\n   - 支持多种签名算法：base64、md5、sha256、hmac_sha256\n   - 可以为不同的接口配置不同的算法\n\n4. **时间戳验证**\n   - 自动验证请求时间戳，防止重放攻击\n   - 可配置时间戳过期时间\n\n5. **自定义错误信息**\n   - 可以为不同的接口配置不同的错误提示信息\n   - 支持国际化消息\n\n---\n\n## 防重复提交\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 使用方式\n\n**注解（推荐单接口）：**\n\n```java\n@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS, message = \"请勿重复提交\")\n```\n\n**YAML（推荐批量）：**\n\n```yaml\nguardian:\n  repeat-submit:\n    urls:\n      - pattern: /api/order/**\n        interval: 10\n        message: \"订单正在处理，请勿重复提交\"\n```\n\n### 全量配置\n\n```yaml\nguardian:\n  repeatable-filter-order: -100000       # 请求体缓存过滤器排序（全局共享，仅需配置一次）\n  repeat-submit:\n    storage: redis                    # redis / local\n    key-encrypt: md5                  # none / md5\n    response-mode: exception          # exception / json\n    log-enabled: false\n    interceptor-order: 2000           # 拦截器排序（值越小越先执行）\n    exclude-urls:\n      - /api/public/**\n    urls:\n      - pattern: /api/order/submit\n        interval: 10\n        time-unit: seconds\n        key-scope: user\n        message: \"请勿重复提交\"\n```\n\n### 防重维度\n\n| 维度 | YAML 值 | 注解值 | 效果 |\n|------|---------|--------|------|\n| 用户级 | `user` | `KeyScope.USER` | 同一用户 + 同一接口 + 同一参数（默认） |\n| IP 级 | `ip` | `KeyScope.IP` | 同一 IP + 同一接口 + 同一参数 |\n| 全局级 | `global` | `KeyScope.GLOBAL` | 同一接口 + 同一参数 |\n\n### 响应模式\n\n| 模式 | 配置值 | 行为 |\n|------|--------|------|\n| 异常模式 | `exception`（默认） | 抛出 `RepeatSubmitException`，由全局异常处理器捕获 |\n| JSON 模式 | `json` | 拦截器直接写入 JSON 响应 |\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Repeat-Submit]`\n- **Actuator**：`GET /actuator/guardianRepeatSubmit`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `top` | 被拦截接口排行返回条数 | 10 |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalBlockCount\": 128,\n  \"totalPassCount\": 5432,\n  \"topBlockedApis\": {\n    \"/api/order/submit\": 56,\n    \"/api/sms/send\": 42\n  }\n}\n```\n\n### 扩展点\n\nGuardian 的核心组件均可替换，注册同类型 Bean 即可覆盖默认实现。\n\n**自定义用户上下文（所有模块共享）：**\n\n```java\n@Bean\npublic UserContext userContext() {\n    // 从你的登录体系中获取当前用户 ID\n    return () -\u003e SecurityUtils.getCurrentUserId();\n}\n```\n\n\u003e 不实现也能用，框架会自动以 SessionId / IP 作为用户标识。\n\n**自定义 Key 生成策略：**\n\n```java\npublic class MyKeyGenerator extends AbstractKeyGenerator {\n\n    public MyKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {\n        super(userContext, keyEncrypt);\n    }\n\n    @Override\n    protected String buildKey(RepeatSubmitKey key) {\n        return key.getServletUri() + \":\" + key.getUserId();\n    }\n}\n\n@Bean\npublic MyKeyGenerator myKeyGenerator(UserContext userContext, AbstractKeyEncrypt keyEncrypt) {\n    return new MyKeyGenerator(userContext, keyEncrypt);\n}\n```\n\n**自定义 Key 加密策略：**\n\n```java\n@Bean\npublic AbstractKeyEncrypt sha256Encrypt() {\n    return new AbstractKeyEncrypt() {\n        @Override\n        public String encrypt(String key) {\n            return DigestUtil.sha256Hex(key);\n        }\n    };\n}\n```\n\n**自定义存储：**\n\n```java\n@Bean\npublic RepeatSubmitStorage myStorage() {\n    return new RepeatSubmitStorage() {\n        @Override\n        public boolean tryAcquire(RepeatSubmitToken token) { /* ... */ }\n        @Override\n        public void release(RepeatSubmitToken token) { /* ... */ }\n    };\n}\n```\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic RepeatSubmitResponseHandler repeatSubmitResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 接口限流\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 两种算法\n\n| | 滑动窗口（默认） | 令牌桶 |\n|--|-----------------|--------|\n| 算法 | 统计窗口内请求数，超过阈值拒绝 | 按速率补充令牌，有令牌放行，无令牌拒绝 |\n| 突发流量 | 不允许，窗口内严格限制 | 允许，桶满时可瞬间消耗所有令牌 |\n| 参数 | `maxCount = qps × window(秒)` | `补充速率 = qps / window(秒)`, `capacity = 桶容量` |\n| 适合场景 | 精确控速的接口 | 允许突发的场景（秒杀、验证码） |\n| 数据结构 | Local: Deque / Redis: ZSET | Local: double + synchronized / Redis: HASH |\n\n### 使用方式\n\n**注解：**\n\n```java\n// 滑动窗口：每秒 10 次，全局\n@RateLimit(qps = 10)\n\n// 令牌桶：每秒补 5 个，桶容量 20，允许突发 20 次\n@RateLimit(qps = 5, capacity = 20, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)\n\n// 令牌桶 + 分钟级：每分钟补 10 个，桶容量 10\n@RateLimit(qps = 10, window = 1, windowUnit = TimeUnit.MINUTES, capacity = 10, algorithm = RateLimitAlgorithm.TOKEN_BUCKET)\n```\n\n**YAML：**\n\n```yaml\nguardian:\n  rate-limit:\n    urls:\n      - pattern: /api/sms/send\n        qps: 1\n        window: 60\n        window-unit: seconds\n        rate-limit-scope: ip\n      - pattern: /api/seckill/**\n        qps: 10\n        capacity: 50\n        algorithm: token_bucket\n        rate-limit-scope: global\n        message: \"抢购太火爆，请稍后重试\"\n```\n\n### 全量配置\n\n```yaml\nguardian:\n  rate-limit:\n    enabled: true                     # 总开关\n    storage: redis                    # redis / local\n    response-mode: exception          # exception / json\n    log-enabled: false\n    interceptor-order: 1000           # 拦截器排序（值越小越先执行）\n    exclude-urls:\n      - /api/public/**\n    urls:\n      - pattern: /api/sms/send\n        qps: 1\n        window: 60\n        window-unit: seconds\n        rate-limit-scope: ip\n      - pattern: /api/order/**\n        qps: 10\n        rate-limit-scope: user\n      - pattern: /api/seckill/**\n        qps: 10\n        capacity: 50\n        algorithm: token_bucket\n        rate-limit-scope: global\n```\n\n### 注解参数\n\n| 参数 | 默认值 | 说明 |\n|------|--------|------|\n| `qps` | `10` | 滑动窗口=QPS，令牌桶=每 window 补充的令牌数 |\n| `window` | `1` | 滑动窗口=窗口跨度，令牌桶=补充周期 |\n| `windowUnit` | `SECONDS` | 时间单位 |\n| `algorithm` | `SLIDING_WINDOW` | 限流算法：`SLIDING_WINDOW` / `TOKEN_BUCKET` |\n| `capacity` | `-1` | 令牌桶容量，≤0 时取 qps 值 |\n| `rateLimitScope` | `GLOBAL` | 限流维度 |\n| `message` | `请求过于频繁，请稍后再试` | 提示信息 |\n\n### 限流维度\n\n| 维度 | YAML 值 | 注解值 | 效果 |\n|------|---------|--------|------|\n| 全局 | `global` | `GLOBAL` | 接口维度，不区分用户和 IP（默认） |\n| IP | `ip` | `IP` | 同一 IP 独立计数 |\n| 用户 | `user` | `USER` | 同一用户独立计数 |\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Rate-Limit]`\n- **Actuator**：`GET /actuator/guardianRateLimit`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `blockedTop` | 被拦截接口排行返回条数 | 10 |\n| `requestTop` | 高频请求接口排行返回条数 | 10 |\n| `detailsTop` | 接口维度明细返回条数 | 10 |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalRequestCount\": 5560,\n  \"totalPassCount\": 5432,\n  \"totalBlockCount\": 128,\n  \"blockRate\": \"2.30%\",\n  \"topBlockedApis\": { \"/api/sms/send\": 56 },\n  \"topRequestApis\": { \"/api/search\": 3200 },\n  \"apiDetails\": {\n    \"/api/sms/send\": { \"requests\": 200, \"passes\": 144, \"blocks\": 56, \"blockRate\": \"28.00%\" }\n  }\n}\n```\n\n### 扩展点\n\n注册同类型 Bean 即可覆盖默认实现。\n\n**自定义用户上下文（与防重模块共享）：**\n\n```java\n@Bean\npublic UserContext userContext() {\n    return () -\u003e SecurityUtils.getCurrentUserId();\n}\n```\n\n**自定义限流 Key 生成策略：**\n\n```java\npublic class MyRateLimitKeyGenerator extends AbstractRateLimitKeyGenerator {\n\n    public MyRateLimitKeyGenerator(UserContext userContext) {\n        super(userContext);\n    }\n\n    @Override\n    protected String buildKey(RateLimitKey key) {\n        return key.getServletUri() + \":\" + key.getUserId();\n    }\n}\n\n@Bean\npublic MyRateLimitKeyGenerator myRateLimitKeyGenerator(UserContext userContext) {\n    return new MyRateLimitKeyGenerator(userContext);\n}\n```\n\n**自定义存储：**\n\n```java\n@Bean\npublic RateLimitStorage myRateLimitStorage() {\n    return new RateLimitStorage() {\n        @Override\n        public boolean tryAcquire(RateLimitToken token) { /* ... */ }\n    };\n}\n```\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic RateLimitResponseHandler rateLimitResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 接口幂等\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 工作流程\n\n1. 客户端调用 `GET /guardian/idempotent/token?key=order-submit` 获取一次性 Token\n2. 客户端携带 Token 发起业务请求（Header 或 Param 方式）\n3. 拦截器校验 Token：存在则消费放行，不存在或已消费则拒绝\n4. （可选）开启结果缓存后，重复请求返回首次执行结果而非拒绝\n\n### 注解参数\n\n| 参数 | 默认值 | 说明 |\n|------|--------|------|\n| `value` | **必填** | 接口唯一标识，用于隔离不同接口的 Token |\n| `from` | `HEADER` | Token 来源：`HEADER` / `PARAM`（PARAM 模式依次查找 URL 参数、表单字段、JSON Body） |\n| `tokenName` | `X-Idempotent-Token` | Header 名 / URL 参数名 / JSON Body 字段名 |\n| `message` | `幂等Token无效或已消费` | 拒绝时的提示信息 |\n\n### 全量配置\n\n```yaml\nguardian:\n  repeatable-filter-order: -100000       # 请求体缓存过滤器排序（全局共享，仅需配置一次）\n  idempotent:\n    enabled: true                     # 总开关\n    storage: redis                    # redis / local\n    timeout: 300                      # Token 有效期（默认 300）\n    time-unit: seconds                # 有效期单位\n    response-mode: exception          # exception / json\n    log-enabled: false\n    interceptor-order: 3000           # 拦截器排序（值越小越先执行）\n    result-advice-order: 200          # 返回值缓存 Advice 排序（默认 200）\n    token-endpoint: true              # 是否注册内置 Token 获取接口\n    result-cache: false               # 是否启用结果缓存\n    missing-token-message: \"请求缺少幂等Token\"  # 缺少 Token 时的提示（支持 i18n Key）\n```\n\n### 结果缓存\n\n开启 `result-cache: true` 后，首次请求的返回值会被缓存，重复请求直接返回缓存结果（而非拒绝）。\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Idempotent]`\n- **Actuator**：`GET /actuator/guardianIdempotent`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `top` | 被拦截接口排行返回条数 | 10 |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalRequestCount\": 1200,\n  \"totalPassCount\": 1100,\n  \"totalBlockCount\": 100,\n  \"blockRate\": \"8.33%\",\n  \"topBlockedApis\": {\n    \"/order/submit\": 60,\n    \"/pay/confirm\": 40\n  }\n}\n```\n\n### 扩展点\n\n**自定义 Token 生成器：**\n\n```java\n@Bean\npublic IdempotentTokenGenerator idempotentTokenGenerator() {\n    return () -\u003e \"custom-\" + UUID.randomUUID().toString();\n}\n```\n\n**自定义存储：**\n\n```java\n@Bean\npublic IdempotentStorage myIdempotentStorage() {\n    return new IdempotentStorage() {\n        @Override\n        public void save(IdempotentToken token) { /* ... */ }\n        @Override\n        public boolean tryConsume(String tokenKey) { /* ... */ }\n    };\n}\n```\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic IdempotentResponseHandler idempotentResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 参数自动Trim\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n自动去除所有请求参数的首尾空格，同时支持不可见字符替换（如零宽空格、BOM、回车符等从复制粘贴混入的脏字符）。同时作用于表单参数（Query / Form）和 JSON Body。\n\n### 全量配置\n\n```yaml\nguardian:\n  auto-trim:\n    enabled: true                      # 总开关（默认 true）\n    filter-order: -10000               # Filter 排序（值越小越先执行）\n    exclude-fields:                    # 排除字段（表单 + JSON Body 统一生效）\n      - password\n      - signature\n    character-replacements:            # 字符替换规则（先替换后 trim）\n      - from: \"\\\\r\"                    # 回车符\n        to: \"\"\n      - from: \"\\\\u200B\"               # 零宽空格\n        to: \"\"\n      - from: \"\\\\uFEFF\"               # BOM\n        to: \"\"\n```\n\n### 字符替换规则\n\n`character-replacements` 支持以下转义格式：\n\n| 转义写法 | 实际字符 | 说明 |\n|---------|---------|------|\n| `\\\\r` | `\\r` | 回车符 |\n| `\\\\n` | `\\n` | 换行符 |\n| `\\\\t` | `\\t` | 制表符 |\n| `\\\\0` | `\\0` | 空字符 |\n| `\\\\uXXXX` | Unicode 字符 | 如 `\\\\u200B` = 零宽空格 |\n\n执行顺序：**先执行字符替换，再执行 trim**。\n\n### 排除字段\n\n密码、签名等不应被 trim 的字段可加入 `exclude-fields`，同时作用于表单参数名和 JSON Body 字段名：\n\n```yaml\nguardian:\n  auto-trim:\n    exclude-fields:\n      - password\n      - signature\n```\n\n\u003c/details\u003e\n\n---\n\n## 慢接口检测\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n自动检测响应时间超过阈值的接口，打印 WARN 日志并记录统计数据。支持全局阈值 + 注解覆盖，通过 Actuator 端点查看 Top N 排行。\n\n### 使用方式\n\n**零配置**：引入 Starter 即可使用，默认阈值 3000ms。\n\n**注解自定义阈值**：\n\n```java\n@SlowApiThreshold(1000)\n@GetMapping(\"/detail\")\npublic Result getDetail(@RequestParam Long id) {\n    return detailService.query(id);\n}\n```\n\n注解优先级高于全局配置。没有注解的接口使用全局阈值。\n\n### 全量配置\n\n```yaml\nguardian:\n  slow-api:\n    enabled: true                      # 总开关（默认 true）\n    threshold: 3000                    # 全局阈值（毫秒，默认 3000）\n    interceptor-order: -1000           # 拦截器排序（值越小越先执行）\n    exclude-urls:                      # 排除规则（白名单，命中直接放行）\n      - /api/health\n      - /api/public/**\n```\n\n### 可观测性\n\n**日志输出**：超过阈值自动打印 WARN 日志，前缀 `[Guardian-Slow-Api]`：\n\n```\nWARN [Guardian-Slow-Api] @SlowApiThreshold 慢接口检测 | Method=GET | URI=/api/detail | 耗时=3521ms | 阈值=3000ms\n```\n\n**Actuator 端点**：`GET /actuator/guardianSlowApi`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `top` | 慢接口排行返回条数 | 10 |\n| `recordTop` | 每个接口的耗时明细记录条数 | 5（最大 100） |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalSlowCount\": 15,\n  \"topSlowApis\": {\n    \"/api/detail\": {\n      \"count\": 8,\n      \"maxDuration\": 5230,\n      \"recentRecords\": [\n        { \"duration\": 5230, \"time\": \"2026-02-09 15:32:10\" },\n        { \"duration\": 4100, \"time\": \"2026-02-09 14:20:45\" }\n      ]\n    },\n    \"/api/export\": {\n      \"count\": 7,\n      \"maxDuration\": 12500,\n      \"recentRecords\": [\n        { \"duration\": 12500, \"time\": \"2026-02-09 11:05:22\" }\n      ]\n    }\n  }\n}\n```\n\n### 扩展点\n\n**自定义慢接口记录器（SPI）：**\n\n默认使用内存环形缓冲记录慢接口数据，可通过注入自定义 Bean 替换为数据库 / ES 等持久化方案：\n\n```java\n@Bean\npublic SlowApiRecorder slowApiRecorder() {\n    return new SlowApiRecorder() {\n        @Override\n        public void record(SlowApiRecord record) { /* 写入数据库 / ES */ }\n        @Override\n        public long getTotalSlowCount() { /* ... */ }\n        @Override\n        public LinkedHashMap\u003cString, Map\u003cString, Object\u003e\u003e getTopSlowApis(int n) { /* ... */ }\n        @Override\n        public List\u003cSlowApiRecord\u003e getRecords(String uri, int limit) { /* ... */ }\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 请求链路追踪\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n自动为每个请求生成唯一的 TraceId，写入 MDC 和响应头。上游服务通过请求头传递 TraceId，下游自动复用，实现跨服务日志串联。内置跨线程传递工具类，`@Async`、`CompletableFuture`、手动线程池等场景均可保持 traceId 不丢失。\n\n### 使用方式\n\n**零配置**：引入 Starter 即可使用。\n\n在 Logback 配置中加入 `%X{traceId}` 即可在日志中打印 TraceId：\n\n```xml\n\u003cpattern\u003e%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n\u003c/pattern\u003e\n```\n\n### 工作流程\n\n```\n请求进入\n  │\n  ▼\nTraceIdFilter（OncePerRequestFilter）\n  ├─ 请求头有 X-Trace-Id → 复用（上游透传）\n  └─ 请求头没有 → 自动生成（时分秒 + 10位随机字符串）\n  │\n  ▼\nMDC.put(\"traceId\", traceId)     ← 写入 MDC，日志自动携带\nresponse.setHeader(headerName)  ← 写入响应头，客户端可获取\n  │\n  ▼\n业务执行（同一线程内所有日志都带 traceId）\n  │\n  ▼\nMDC.remove(\"traceId\")           ← 请求结束清理\n```\n\n### 跨服务链路串联\n\n上游服务调用下游时，把响应头中的 TraceId 传递到下游请求头即可：\n\n```\n服务 A 收到请求 → 生成 traceId=abc123 → 调用服务 B 时带上 X-Trace-Id: abc123\n服务 B 收到请求 → 从请求头取出 abc123 → 复用同一个 traceId\n```\n\n同一条链路上所有服务的日志都带 `abc123`，排查问题时按 TraceId 搜索即可。\n\n### 跨线程 TraceId 传递\n\nMDC 基于 ThreadLocal，新开子线程时 traceId 会丢失。Guardian 提供以下工具解决：\n\n**1. 手动包装 Runnable / Callable**\n\n```java\nexecutor.submit(TraceUtils.wrap(() -\u003e {\n    // 子线程中 traceId 与父线程一致\n    log.info(\"异步任务执行\");\n}));\n\nFuture\u003cString\u003e future = executor.submit(TraceUtils.wrap(() -\u003e {\n    log.info(\"带返回值的异步任务\");\n    return \"result\";\n}));\n```\n\n**2. 包装 Executor（适用于 CompletableFuture）**\n\n```java\nExecutor tracedExecutor = TraceUtils.wrap(myExecutor);\nCompletableFuture.runAsync(() -\u003e {\n    log.info(\"CompletableFuture 中也有 traceId\");\n}, tracedExecutor);\n```\n\n**3. @Async 线程池自动传递**\n\n```java\n@Bean\npublic ThreadPoolTaskExecutor asyncExecutor() {\n    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n    executor.setCorePoolSize(10);\n    executor.setTaskDecorator(new TraceTaskDecorator());\n    executor.initialize();\n    return executor;\n}\n```\n\n配置 `TraceTaskDecorator` 后，所有 `@Async` 任务自动携带父线程的 traceId，无需手动包装。\n\n| 类名 | 场景 | 用法 |\n|------|------|------|\n| `TraceRunnable` | 手动提交 Runnable | `new TraceRunnable(task)` 或 `TraceUtils.wrap(task)` |\n| `TraceCallable` | 手动提交 Callable | `new TraceCallable\u003c\u003e(task)` 或 `TraceUtils.wrap(task)` |\n| `TraceTaskDecorator` | @Async 线程池 | 设置到 `ThreadPoolTaskExecutor.setTaskDecorator()` |\n| `TraceUtils` | 工具类 | `wrap(Runnable)` / `wrap(Callable)` / `wrap(Executor)` / `getTraceId()` / `switchTraceId()` |\n\n### MQ 消息链路追踪\n\nGuardian 支持 RabbitMQ、Kafka、RocketMQ 三种消息队列的 TraceId 自动传递，发送端自动注入 traceId，消费端通过 AOP 切面自动提取并写入 MDC。\n\n**核心设计**：消费端采用 AOP 切面拦截 Listener 方法，不占用框架原生拦截器/Hook 位置，接入方可自由注册自己的拦截器。\n\n\u003e **注意**：AOP 切面从方法参数中提取 traceId，因此 Listener 方法的参数类型必须使用消息原始类型：\n\u003e - RabbitMQ：`Message`（`org.springframework.amqp.core.Message`），不能用 `String`\n\u003e - Kafka：`ConsumerRecord\u003c?, ?\u003e`，不能用 `String`\n\u003e - RocketMQ：`MessageExt`（`org.apache.rocketmq.common.message.MessageExt`）\n\n#### RabbitMQ\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-trace-rabbitmq\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n- **发送端**：通过 `MessagePostProcessor` 将 traceId 写入消息 Header（additive，不冲突）\n- **消费端**：AOP 切面拦截 `@RabbitListener` / `@RabbitHandler` 方法，自动从消息 Header 提取 traceId\n\n#### Kafka\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-trace-kafka\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n发送端需在 Kafka Producer 配置中注册拦截器：\n\n```yaml\nspring:\n  kafka:\n    producer:\n      properties:\n        interceptor.classes: com.sun.guardian.trace.kafka.interceptor.TraceKafkaProducerInterceptor\n```\n\n- **发送端**：通过 `ProducerInterceptor` 将 traceId 写入消息 Header（不占用 RecordInterceptor）\n- **消费端**：AOP 切面拦截 `@KafkaListener` / `@KafkaHandler` 方法，自动从消息 Header 提取 traceId\n\n#### RocketMQ\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.biggg-guardian\u003c/groupId\u003e\n    \u003cartifactId\u003eguardian-trace-rocketmq\u003c/artifactId\u003e\n    \u003cversion\u003e1.10.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n- **发送端**：通过 `SendMessageHook` 将 traceId 写入消息 UserProperty（支持注册多个 Hook，不冲突）\n- **消费端**：AOP 切面拦截 `@RocketMQMessageListener` 注解的类，自动从消息 UserProperty 提取 traceId\n\n#### 批量消费场景\n\nAOP 切面自动设置**第一条**消息的 traceId。如需在批量消费中逐条切换 traceId，各模块提供了专用工具类，一行代码即可完成切换：\n\n| 模块 | 工具类 | 用法 |\n|------|--------|------|\n| RabbitMQ | `TraceRabbitUtils` | `TraceRabbitUtils.switchTraceId(message)` |\n| Kafka | `TraceKafkaUtils` | `TraceKafkaUtils.switchTraceId(record)` |\n| RocketMQ | `TraceRocketMQUtils` | `TraceRocketMQUtils.switchTraceId(messageExt)` |\n\n工具类内部自动从消息 Header / UserProperty 中提取 traceId 并切换 MDC，headerName 通过 `TraceConfig` 动态获取，支持配置中心热更新。\n\n**RabbitMQ 批量消费示例**：\n\n```java\n@RabbitListener(queues = \"myQueue\", containerFactory = \"batchContainerFactory\")\npublic void handleBatch(List\u003cMessage\u003e messages) {\n    for (Message msg : messages) {\n        TraceRabbitUtils.switchTraceId(msg);\n        // 业务逻辑...\n    }\n}\n```\n\n**Kafka 批量消费示例**：\n\n```java\n@KafkaListener(topics = \"myTopic\", batch = \"true\")\npublic void handleBatch(List\u003cConsumerRecord\u003cString, String\u003e\u003e records) {\n    for (ConsumerRecord\u003cString, String\u003e record : records) {\n        TraceKafkaUtils.switchTraceId(record);\n        // 业务逻辑...\n    }\n}\n```\n\n\u003e **RocketMQ 说明**：RocketMQ Spring Boot Starter 的 `RocketMQListener` 本身是逐条回调，AOP 切面会自动为每次调用注入/清理 traceId，通常无需手动切换。如有特殊场景，也可使用 `TraceRocketMQUtils.switchTraceId(messageExt)`。\n\n### 全量配置\n\n```yaml\nguardian:\n  trace:\n    enabled: true                      # 总开关（默认 true）\n    filter-order: -30000               # Filter 排序（值越小越先执行，确保最先执行）\n    header-name: X-Trace-Id            # 请求头/响应头名称（默认 X-Trace-Id）\n```\n\n\u003c/details\u003e\n\n---\n\n## IP黑白名单\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n基于 IP 的访问控制，支持两种模式：\n\n- **全局黑名单**：匹配的 IP 拒绝访问所有接口\n- **URL 绑定白名单**：指定接口仅允许白名单 IP 访问，其余 IP 拒绝\n\n匹配优先级：**全局黑名单 \u003e URL 绑定白名单 \u003e 放行**\n\nIP 规则支持三种格式：\n\n| 格式 | 示例 | 说明 |\n|------|------|------|\n| 精确匹配 | `192.168.1.100` | 精确匹配单个 IP |\n| 通配符 | `192.168.1.*` | 匹配整个网段 |\n| CIDR | `10.0.0.0/8` | CIDR 网段匹配 |\n\n### 全量配置\n\n```yaml\nguardian:\n  ip-filter:\n    enabled: true                       # 总开关（默认 false，需显式开启）\n    response-mode: json                 # exception / json\n    log-enabled: true                   # 是否打印拦截日志（默认 false）\n    message: \"IP 访问被拒绝\"              # 拒绝提示信息（支持 i18n Key）\n    filter-order: -20000                # Filter 排序（值越小越先执行）\n    black-list:                         # 全局 IP 黑名单\n      - 192.168.100.100\n      - 10.0.0.0/8\n    urls:                               # URL 绑定白名单\n      - pattern: /admin/**\n        white-list:\n          - 127.0.0.1\n          - 192.168.1.*\n      - pattern: /internal/api/**\n        white-list:\n          - 10.0.0.0/8\n```\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Ip-Filter]`\n- **Actuator**：`GET /actuator/guardianIpFilter`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `blockTop` | 黑名单拦截 IP 排行返回条数 | 10 |\n| `whiteTop` | 白名单拦截请求排行返回条数 | 10 |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalBlackListBlockCount\": 56,\n  \"totalWhiteListBlockCount\": 23,\n  \"topBlackListBlocked\": {\n    \"192.168.100.100\": 42,\n    \"10.1.2.3\": 14\n  },\n  \"topWhiteListBlocked\": {\n    \"/admin/dashboard | 172.16.0.5\": 15,\n    \"/internal/api/config | 192.168.2.1\": 8\n  }\n}\n```\n\n### 扩展点\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic IpFilterResponseHandler ipFilterResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setStatus(HttpServletResponse.SC_FORBIDDEN);\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 防重放攻击\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n防止攻击者截获合法请求后重复发送（Replay Attack）。客户端在请求头中携带时间戳和一次性随机标识（Nonce），服务端进行双重校验：\n\n1. **Timestamp 校验**：请求时间戳与服务器时间差超过 `max-age` 则拒绝（请求过期）\n2. **Nonce 校验**：Nonce 已存在于存储中则拒绝（重放攻击），不存在则记录并放行\n\n\u003e **安全设计**：Nonce 的存活时间（`nonce-ttl`，默认 24h）远大于 Timestamp 有效窗口（`max-age`，默认 60s）。二者解耦可防止攻击者在 Nonce 过期后篡改 Timestamp 重放请求。搭配 `guardian-sign` 签名模块使用时，签名防止 Timestamp 被篡改，`nonce-ttl` 可缩短至与 `max-age` 相同。\n\n### 校验流程\n\n```\n请求进入 AntiReplayFilter\n  │\n  ├─ 1. 排除规则匹配（exclude-urls）→ 命中直接放行\n  │\n  ├─ 2. URL 规则匹配（urls）→ 未命中则放行\n  │\n  ├─ 3. 取 X-Timestamp，判断与服务器时间差\n  │      ↓ 超过 maxAge → 拒绝（请求过期）\n  │\n  ├─ 4. 取 X-Nonce，判断是否已使用\n  │      ↓ Storage 中已存在 → 拒绝（重放攻击）\n  │\n  ├─ 5. 校验通过\n  │      将 Nonce 存入 Storage（TTL = nonceTtl）\n  │      放行\n  ▼\n```\n\n### 客户端请求示例\n\n```\nPOST /api/payment/submit HTTP/1.1\nX-Timestamp: 1708000000000\nX-Nonce: a1b2c3d4e5f6g7h8\nContent-Type: application/json\n\n{\"orderId\": \"ORD001\", \"amount\": 99.99}\n```\n\n### 全量配置\n\n```yaml\nguardian:\n  anti-replay:\n    enabled: true                        # 总开关（默认 true）\n    storage: redis                       # redis / local\n    max-age: 60                          # timestamp 有效窗口（默认 60）\n    max-age-unit: seconds                # timestamp 有效窗口单位（默认秒）\n    nonce-ttl: 86400                     # nonce 存活时间（默认 86400 = 24h，必须 \u003e= max-age）\n    nonce-ttl-unit: seconds              # nonce 存活时间单位（默认秒）\n    timestamp-header: X-Timestamp        # 时间戳请求头名称\n    nonce-header: X-Nonce                # Nonce 请求头名称\n    response-mode: json                  # exception / json\n    log-enabled: true                    # 是否打印拦截日志\n    filter-order: -14000                 # Filter 排序\n    missing-timestamp-message: \"缺少时间戳\"   # 提示信息（支持 i18n Key）\n    missing-nonce-message: \"缺少请求标识\"\n    expired-message: \"请求已过期\"\n    replay-message: \"重复请求\"\n    urls:                                # 需要防重放的 URL（AntPath）\n      - pattern: /api/payment/**\n      - pattern: /api/transfer/**\n    exclude-urls:                        # 排除规则（白名单，优先级最高）\n      - /api/public/**\n```\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Anti-Replay]`\n- **Actuator**：`GET /actuator/guardianAntiReplay`\n\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `top` | 被拦截接口排行返回条数 | 10 |\n\n\u003e 所有参数均为可选，不传参数与原调用方式完全一致。\n\n```json\n{\n  \"totalRequestCount\": 5000,\n  \"totalPassCount\": 4980,\n  \"totalBlockCount\": 20,\n  \"blockRate\": \"0.40%\",\n  \"topBlockedApis\": {\n    \"/api/payment/submit\": 12,\n    \"/api/transfer/confirm\": 8\n  }\n}\n```\n\n### 扩展点\n\n**自定义 Nonce 存储：**\n\n```java\n@Bean\npublic NonceStorage nonceStorage() {\n    return new NonceStorage() {\n        @Override\n        public boolean tryAcquire(String nonce, long nonceTtl, TimeUnit nonceTtlUnit) {\n            // 自定义存储逻辑（如 Memcached、数据库等）\n        }\n    };\n}\n```\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic AntiReplayResponseHandler antiReplayResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 接口开关\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n基于yml/actuator实现接口的关闭/开启控制，接口路径支持AntPath\n\n### 全量配置\n\n```yaml\nguardian:\n  api-switch:\n    enabled: true                        # 总开关（默认 true）\n    response-mode: json                  # exception / json\n    message: \"接口暂时关闭，请稍后再试\"    # 提示信息（支持 i18n Key）\n    log-enabled: true                    # 是否打印拦截日志\n    interceptor-order: 500               # 拦截器排序\n    disabled-urls:                       # 启动时默认关闭的接口\n      - /api-switch/disabled\n```\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Api-Switch]`\n- **Actuator**：`GET /actuator/guardianApiSwitch`\n\n```json\n{\n  \"disabledUrls\": [\n    \"/api-switch/disabled\"\n  ]\n}\n```\n\n### 关闭接口\n\n- **Actuator**：`POST /actuator/guardianApiSwitch/{urlPattern}`\n\u003e\u003csmall\u003e`关闭接口`需将接口内`{urlPattern}`替换为实际接口路径，例如：/actuator/guardianApiSwitch/api-switch/disabled \u003c/small\u003e\n\n```json\n{\n  \"action\": \"disabled\",\n  \"urlPattern\": \"/api-switch/disabled\",\n  \"disabledUrls\": [\n    \"/123\",\n    \"/api-switch/disabled\"\n  ]\n}\n```\n\n### 开启接口\n\n- **Actuator**：`DELETE /actuator/guardianApiSwitch/{urlPattern}`\n\u003e\u003csmall\u003e`开启接口`需将接口内`{urlPattern}`替换为实际接口路径，例如：/actuator/guardianApiSwitch/api-switch/disabled \u003c/small\u003e\n\n```json\n{\n  \"action\": \"enabled\",\n  \"urlPattern\": \"/api-switch/disabled\",\n  \"disabledUrls\": [\n    \"/123\"\n  ]\n}\n```\n\n### 扩展点\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic ApiSwitchResponseHandler apiSwitchResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setStatus(HttpServletResponse.SC_OK);\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 参数签名\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n对接口请求参数进行签名验证，防止参数被篡改。支持多种签名算法，包括 BASE64、MD5、SHA256 和 HMAC-SHA256。同时支持响应结果签名，确保返回数据的完整性。\n\n### 使用方式\n\n**注解：**\n\n```java\n// 使用默认算法（BASE64）\n@SignVerify\n\n// 指定算法\n@SignVerify(algorithm = SignAlgorithm.SHA256)\n\n// 自定义错误信息\n@SignVerify(algorithm = SignAlgorithm.HMAC_SHA256, signVerifyMessage = \"签名验证失败\")\n```\n\n**YAML 批量配置：**\n\n```yaml\nguardian:\n  sign:\n    urls:\n      - pattern: /api/payment/**\n        algorithm: hmac_sha256\n        sign-verify-message: \"支付接口签名验证失败\"\n      - pattern: /api/order/**\n        algorithm: sha256\n```\n\n### 全量配置\n\n```yaml\nguardian:\n  sign:\n    enabled: true                        # 总开关（默认 true）\n    secret-key: your-secret-key          # 签名密钥\n    result-sign: false                   # 结果签名开关（默认 false）\n    response-mode: exception             # exception / json\n    log-enabled: false                   # 是否打印拦截日志\n    interceptor-order: 4000             # 拦截器排序（值越小越先执行）\n    sign-header: X-Sign                  # 签名请求头名称\n    timestamp-header: X-Sign-Timestamp   # 时间戳请求头名称\n    max-age: 60                          # 时间戳过期时间（秒）\n    max-age-unit: seconds                # 时间戳过期时间单位\n    missing-timestamp-message: \"缺少时间戳\"    # 缺少时间戳提示（支持 i18n Key）\n    missing-sign-message: \"缺少参数签名\"      # 缺少签名提示（支持 i18n Key）\n    expired-message: \"请求已过期\"            # 请求过期提示（支持 i18n Key）\n    urls:                                # 需要签名验证的 URL 规则\n      - pattern: /api/payment/**\n        algorithm: hmac_sha256\n        sign-verify-message: \"签名验证失败\"\n```\n\n### 支持的签名算法\n\n| 算法 | YAML 值 | 注解值 | 说明 |\n|------|---------|--------|------|\n| BASE64 | `base64` | `SignAlgorithm.BASE64` | 基于 BASE64 的简单编码 |\n| MD5 | `md5` | `SignAlgorithm.MD5` | MD5 哈希算法 |\n| SHA256 | `sha256` | `SignAlgorithm.SHA256` | SHA256 哈希算法 |\n| HMAC-SHA256 | `hmac_sha256` | `SignAlgorithm.HMAC_SHA256` | 基于密钥的 HMAC-SHA256 算法 |\n| SM3 | `sm3` | `SignAlgorithm.SM3` | 国密 SM3 哈希算法 |\n\n### 签名计算规则\n\n1. **参数排序**：对请求参数（包括 Query 参数和 JSON Body）按字典序排序\n2. **拼接字符串**：`param1=value1\u0026param2=value2\u0026timestamp={timestamp}\u0026key={secretKey}`\n3. **计算签名**：使用指定的算法对拼接后的字符串进行计算\n4. **验证签名**：将计算结果与请求头中的 `X-Sign` 进行比较\n\n### 前端实现参考\n\n前端实现请参考示例项目中的测试页面：\n- **文件位置**：`guardian-example/src/main/resources/static/sign-test.html`\n- **关键依赖**：CryptoJS（MD5/SHA256/HMAC-SHA256）、sm-crypto（SM3）\n\n**核心实现要点：**\n1. **BASE64 算法（GET 请求）**：只使用 Query 参数，不使用 Body\n2. **其他算法（POST 请求）**：只使用 Body，不使用 Query 参数\n3. **响应验签**：后端对整个响应体进行签名，前端需使用完整响应数据验签\n\n\n### 响应结果签名\n\n开启 `result-sign: true` 后，响应结果会自动添加签名头：\n- `X-Sign`：响应结果的签名\n- `X-Sign-Timestamp`：签名时的时间戳\n\n**特别说明**：结果参数签名时会将controller返回的所有信息都进行签名，包括code、message、data等完整的响应结构。例如：\n\n```json\n{\n  \"code\": 200,\n  \"message\": \"success\",\n  \"data\": {\n    \"request\": {\n      \"name\": \"guardian\",\n      \"value\": \"test123\"\n    },\n    \"message\": \"This response is signed\"\n  },\n  \"timestamp\": 1772534163534\n}\n```\n\n签名会基于上述完整的JSON结构进行计算，确保整个响应结果的完整性。\n\n### 可观测性\n\n- **拦截日志**：`log-enabled: true`，前缀 `[Guardian-Sign]`\n- **Actuator**：`GET /actuator/guardianSign`\n\n```json\n{\n  \"totalRequestCount\": 1000,\n  \"totalPassCount\": 990,\n  \"totalBlockCount\": 10,\n  \"blockRate\": \"1.00%\",\n  \"topBlockedApis\": {\n    \"/api/payment/submit\": 5,\n    \"/api/order/create\": 3\n  }\n}\n```\n\n### 扩展点\n\n**自定义签名服务：**\n\n```java\n@Bean\npublic SignService customSignService() {\n    return new SignService() {\n        @Override\n        public String sign(SortedMap\u003cString, String\u003e params, String timestamp, String secretKey, SignAlgorithm algorithm) {\n            // 自定义签名逻辑\n        }\n    };\n}\n```\n\n**自定义响应处理器（仅 `response-mode: json` 时生效）：**\n\n```java\n@Bean\npublic SignResponseHandler signResponseHandler() {\n    return (request, response, code, data, message) -\u003e {\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(JSONUtil.toJsonStr(CommonResult.result(code, data, message)));\n    };\n}\n```\n\u003c/details\u003e\n\n---\n\n## 请求加解密\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e展开查看完整文档\u003c/b\u003e\u003c/summary\u003e\n\n### 功能说明\n\n对接口请求参数进行解密，对响应结果进行加密，确保数据传输的安全性。支持两种加密方案：\n- **RSA + AES**：RSA 加密 AES 密钥，AES 加密数据\n- **SM2 + SM4**：国密算法，SM2 加密 SM4 密钥，SM4 加密数据\n\n### 使用方式\n\n**YAML 配置：**\n\n```yaml\nguardian:\n  # 请求解密配置\n  decrypt:\n    enabled: true\n    key:\n      private-key: \"your-private-key-base64\"    # 解密密钥时使用的私钥\n    data:\n      data-key-header: \"X-Encrypt-Key\"          # 数据密钥请求头名称\n    urls:\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa                      # 密钥加密算法：rsa / sm2\n        data-algorithm: aes                     # 数据加密算法：aes / sm4\n\n  # 响应加密配置\n  encrypt:\n    enabled: true\n    key:\n      public-key: \"your-public-key-base64\"      # 加密密钥时使用的公钥\n    data:\n      data-key-header: \"X-Encrypt-Key\"          # 数据密钥响应头名称\n    urls:\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa\n        data-algorithm: aes\n```\n\n### 全量配置\n\n```yaml\nguardian:\n  decrypt:\n    enabled: true                               # 总开关（默认 true）\n    filter-order: -40000                        # Filter 排序（默认 -40000）\n    log-enabled: false                          # 是否打印拦截日志（默认 false）\n    response-mode: exception                    # 响应模式：exception / json（默认 exception）\n    key:\n      private-key: \"\"                           # 私钥（Base64 格式）\n    data:\n      param-alias: \"encryptParam\"               # param 数据加密后别名（默认 encryptParam）\n      body-alias: \"encryptBody\"                 # body 数据加密后别名（默认 encryptBody）\n      data-key-header: \"X-Encrypt-Key\"          # 数据密钥请求头名称（默认 X-Encrypt-Key）\n    missing-data-key-header-message: \"缺少数据密钥请求头\"  # 缺少密钥头提示\n    urls:                                       # 需要解密的 URL 规则\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa\n        data-algorithm: aes\n    exclude-urls:                               # 排除规则（白名单）\n      - /api/public/**\n\n  encrypt:\n    enabled: true                               # 总开关（默认 true）\n    result-advice-order: 300                    # 返回值加密 Advice 排序（默认 300）\n    key:\n      public-key: \"\"                            # 公钥（Base64 格式）\n    data:\n      param-alias: \"encryptParam\"               # param 数据加密后别名（默认 encryptParam）\n      body-alias: \"encryptBody\"                 # body 数据加密后别名（默认 encryptBody）\n      data-key-header: \"X-Encrypt-Key\"          # 数据密钥响应头名称（默认 X-Encrypt-Key）\n      key-mode: dynamic                         # 密钥模式：dynamic / static（默认 dynamic）\n      key: \"\"                                   # 静态密钥（key-mode=static 时使用）\n    urls:                                       # 需要加密的 URL 规则\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa\n        data-algorithm: aes\n    exclude-urls:                               # 排除规则（白名单）\n      - /api/public/**\n```\n\n### 支持的加密算法\n\n| 密钥算法 | YAML 值 | 说明 |\n|---------|---------|------|\n| RSA | `rsa` | RSA 加密 AES 密钥 |\n| SM2 | `sm2` | 国密 SM2 加密 SM4 密钥 |\n\n| 数据算法 | YAML 值 | 说明 |\n|---------|---------|------|\n| AES | `aes` | AES-GCM 模式加密数据 |\n| SM4 | `sm4` | 国密 SM4-CBC 模式加密数据 |\n\n### 工作流程\n\n**请求解密流程：**\n\n```\n客户端请求\n  │\n  ├─ 1. 生成随机数据密钥（AES/SM4）\n  ├─ 2. 使用数据密钥加密请求参数（param 或 body）\n  ├─ 3. 使用服务端公钥加密数据密钥\n  ├─ 4. 发送请求：\n  │      Header: X-Encrypt-Key: {加密后的密钥}\n  │      Param: encryptParam={加密后的参数} 或\n  │      Body: {\"encryptBody\": \"{加密后的数据}\"}\n  │\n  ▼\n服务端 DecryptFilter\n  │\n  ├─ 1. 从 Header 获取加密的密钥\n  ├─ 2. 使用私钥解密得到数据密钥\n  ├─ 3. 从 param 或 body 中获取加密数据\n  ├─ 4. 使用数据密钥解密数据\n  ├─ 5. 将解密后的数据传递给业务层\n  │\n  ▼\n业务处理\n```\n\n**响应加密流程：**\n\n```\n业务层返回数据\n  │\n  ▼\nEncryptResponseAdvice\n  │\n  ├─ 1. 生成随机数据密钥（或使用静态密钥）\n  ├─ 2. 使用数据密钥加密响应数据\n  ├─ 3. 使用客户端公钥加密数据密钥\n  ├─ 4. 返回响应：\n  │      Header: X-Encrypt-Key: {加密后的密钥}\n  │      Body: {\"encryptBody\": \"{加密后的数据}\"}\n  │\n  ▼\n客户端\n  │\n  ├─ 1. 从 Header 获取加密的密钥\n  ├─ 2. 使用私钥解密得到数据密钥\n  ├─ 3. 使用数据密钥解密响应数据\n  │\n  ▼\n业务处理\n```\n\n### 密钥模式\n\n支持两种密钥模式：\n\n| 模式 | YAML 值 | 说明 |\n|------|---------|------|\n| 动态密钥 | `dynamic` | 每次请求动态生成密钥（默认） |\n| 静态密钥 | `static` | 使用配置的固定密钥 |\n\n### 前端实现参考\n\n前端实现请参考示例项目中的测试页面：\n- **文件位置**：`guardian-example/src/main/resources/static/encrypt-test.html`\n- **关键依赖**：CryptoJS（AES）、sm-crypto（SM2/SM4）、jsencrypt（RSA）\n\n**核心实现要点：**\n\n1. **RSA + AES 方案**\n   - 生成 16 字节 AES 密钥\n   - 使用 AES-GCM 模式加密数据\n   - 使用 RSA 公钥加密 AES 密钥\n\n2. **SM2 + SM4 方案**\n   - 生成 16 字节 SM4 密钥\n   - 使用 SM4-CBC 模式加密数据\n   - 使用 SM2 公钥加密 SM4 密钥（C1C3C2 格式）\n\n### 扩展点\n\n**自定义密钥解密服务：**\n\n```java\n@Bean\npublic KeyDecryptService customKeyDecryptService() {\n    return new KeyDecryptService() {\n        @Override\n        public String decrypt(String encryptedKey, String privateKey) {\n            // 自定义密钥解密逻辑\n        }\n        @Override\n        public KeyEncryptAlgorithm getAlgorithm() {\n            return KeyEncryptAlgorithm.RSA;\n        }\n    };\n}\n```\n\n**自定义数据解密服务：**\n\n```java\n@Bean\npublic DataDecryptService customDataDecryptService() {\n    return new DataDecryptService() {\n        @Override\n        public String decrypt(String encryptedData, String key) {\n            // 自定义数据解密逻辑\n        }\n        @Override\n        public DataEncryptAlgorithm getAlgorithm() {\n            return DataEncryptAlgorithm.AES;\n        }\n    };\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## 规则优先级\n\nGuardian 各模块（防重复提交、接口限流、慢接口检测）的规则匹配遵循以下优先级：\n\n```\nexclude-urls（白名单）\u003e YAML 规则 \u003e 注解 \u003e 放行\n```\n\n| 场景 | 行为 |\n|------|------|\n| URL 命中 `exclude-urls` | **直接放行**，跳过所有检测（包括注解） |\n| URL 命中 YAML `urls` 规则 | YAML 规则生效 |\n| 方法有注解 `@RateLimit` / `@RepeatSubmit` / `@SlowApiThreshold` | 注解规则生效 |\n| 以上均未命中 | 放行 |\n\n\u003e **设计理念**：`exclude-urls` 作为白名单拥有最高优先级，可在紧急情况下通过配置中心动态添加 URL 实现\"一键放行\"，无需改代码重启。注解适合\"长期固定\"的保护策略，YAML 规则适合\"动态可调\"的批量策略。\n\n---\n\n## 动态配置\n\nGuardian 所有模块的 YAML 配置均支持通过配置中心（Nacos、Apollo 等）动态刷新，**无需重启应用**即可生效。\n\n### 支持动态刷新的配置项\n\n以下列出所有模块可在配置中心动态修改的完整参数（`enabled`、`storage` 等启动期参数修改后需重启生效）。\n\n**防重复提交**（prefix: `guardian.repeat-submit`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高，AntPath） |\n| `urls` | `List` | `[]` | 防重规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].interval` | `int` | `5` | 防重间隔 |\n| `urls[].time-unit` | `TimeUnit` | `seconds` | 间隔时间单位 |\n| `urls[].key-scope` | `user` / `ip` / `global` | `user` | 防重维度 |\n| `urls[].sp-el` | `String` | — | SpEL 表达式，动态生成 Key（v1.9.0+，如 `#orderId`） |\n| `urls[].message` | `String` | `您的请求过于频繁，请稍后再试` | 拦截提示信息 |\n| `urls[].client-type` | `pc` / `app` | `pc` | 客户端类型 |\n\n**接口限流**（prefix: `guardian.rate-limit`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高，AntPath） |\n| `urls` | `List` | `[]` | 限流规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].qps` | `int` | `10` | 滑动窗口=QPS，令牌桶=每 window 补充令牌数 |\n| `urls[].window` | `int` | `1` | 滑动窗口=窗口跨度，令牌桶=补充周期 |\n| `urls[].window-unit` | `TimeUnit` | `seconds` | 时间单位 |\n| `urls[].algorithm` | `sliding_window` / `token_bucket` | `sliding_window` | 限流算法 |\n| `urls[].capacity` | `int` | `-1` | 令牌桶容量（≤0 时取 qps 值） |\n| `urls[].rate-limit-scope` | `global` / `ip` / `user` | `global` | 限流维度 |\n| `urls[].sp-el` | `String` | — | SpEL 表达式，动态生成 Key（v1.9.0+，如 `#orderId`） |\n| `urls[].message` | `String` | `请求过于频繁，请稍后再试` | 拦截提示信息 |\n\n**接口幂等**（prefix: `guardian.idempotent`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `timeout` | `long` | `300` | Token 有效期 |\n| `time-unit` | `TimeUnit` | `seconds` | 有效期单位 |\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n\n**参数自动Trim**（prefix: `guardian.auto-trim`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `exclude-fields` | `Set\u003cString\u003e` | `[]` | 排除字段（不做 trim 的字段名） |\n| `character-replacements` | `List` | `[]` | 字符替换规则列表，每项参数如下 |\n| `character-replacements[].from` | `String` | — | 待替换字符（支持 `\\\\r` `\\\\n` `\\\\t` `\\\\uXXXX` 转义） |\n| `character-replacements[].to` | `String` | `\"\"` | 替换为 |\n\n**慢接口检测**（prefix: `guardian.slow-api`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `threshold` | `long` | `3000` | 全局慢接口阈值（毫秒） |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高，AntPath） |\n\n**请求链路追踪**（prefix: `guardian.trace`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `header-name` | `String` | `X-Trace-Id` | 请求头/响应头名称（同时影响 MQ 链路追踪的 Header Key） |\n\n**防重放攻击**（prefix: `guardian.anti-replay`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `max-age` | `long` | `60` | timestamp 有效窗口 |\n| `max-age-unit` | `TimeUnit` | `seconds` | timestamp 有效窗口单位 |\n| `nonce-ttl` | `long` | `86400` | nonce 存活时间（必须 \u003e= max-age） |\n| `nonce-ttl-unit` | `TimeUnit` | `seconds` | nonce 存活时间单位 |\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `missing-timestamp-message` | `String` | `缺少时间戳` | 缺少时间戳提示（支持 i18n Key） |\n| `missing-nonce-message` | `String` | `缺少请求标识` | 缺少 Nonce 提示（支持 i18n Key） |\n| `expired-message` | `String` | `请求已过期` | 请求过期提示（支持 i18n Key） |\n| `replay-message` | `String` | `重复请求` | 重放攻击提示（支持 i18n Key） |\n| `urls` | `List` | `[]` | 防重放 URL 规则列表（AntPath） |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高） |\n\n**IP黑白名单**（prefix: `guardian.ip-filter`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `message` | `String` | `IP 访问被拒绝` | 拒绝提示信息（支持 i18n Key） |\n| `black-list` | `List\u003cString\u003e` | `[]` | 全局 IP 黑名单（精确 / 通配符 / CIDR） |\n| `urls` | `List` | `[]` | URL 绑定白名单规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].white-list` | `List\u003cString\u003e` | `[]` | 允许访问的 IP 列表（精确 / 通配符 / CIDR） |\n\n**接口开关**（prefix: `guardian.api-switch`）\n\n| YAML Key | 类型 | 默认值 | 说明                 |\n|----------|------|--------|--------------------|\n| `response-mode` | `exception` / `json` | `exception` | 响应模式               |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志           |\n| `message` | `String` | `接口暂时关闭，请稍后再试` | 提示信息（支持 i18n Key）  |\n| `disabled-urls` | `List\u003cString\u003e` | `[]` | 默认关闭的接口路径（AntPath） |\n\n**参数签名**（prefix: `guardian.sign`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `secret-key` | `String` | `\"\"` | 签名密钥 |\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `sign-header` | `String` | `X-Sign` | 签名请求头名称 |\n| `timestamp-header` | `String` | `X-Sign-Timestamp` | 时间戳请求头名称 |\n| `max-age` | `long` | `60` | 时间戳过期时间 |\n| `max-age-unit` | `TimeUnit` | `seconds` | 时间戳过期时间单位 |\n| `missing-timestamp-message` | `String` | `缺少时间戳` | 缺少时间戳提示（支持 i18n Key） |\n| `missing-sign-message` | `String` | `缺少参数签名` | 缺少签名提示（支持 i18n Key） |\n| `expired-message` | `String` | `请求已过期` | 请求过期提示（支持 i18n Key） |\n| `urls` | `List` | `[]` | 签名验证规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].algorithm` | `base64` / `md5` / `sha256` / `hmac_sha256` / `sm3` | `base64` | 签名算法 |\n| `urls[].sign-verify-message` | `String` | `签名校验失败` | 签名验证失败提示（支持 i18n Key） |\n\n**请求解密**（prefix: `guardian.decrypt`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `enabled` | `boolean` | `true` | 总开关 |\n| `filter-order` | `int` | `-40000` | Filter 排序（值越小越先执行） |\n| `log-enabled` | `boolean` | `false` | 是否打印拦截日志 |\n| `response-mode` | `exception` / `json` | `exception` | 响应模式 |\n| `key.private-key` | `String` | `\"\"` | 私钥（Base64 格式，用于解密密钥） |\n| `data.param-alias` | `String` | `encryptParam` | param 数据加密后别名 |\n| `data.body-alias` | `String` | `encryptBody` | body 数据加密后别名 |\n| `data.data-key-header` | `String` | `X-Encrypt-Key` | 数据密钥请求头名称 |\n| `missing-data-key-header-message` | `String` | `缺少数据密钥请求头` | 缺少密钥头提示（支持 i18n Key） |\n| `urls` | `List` | `[]` | 解密规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].key-algorithm` | `rsa` / `sm2` | `rsa` | 密钥加密算法 |\n| `urls[].data-algorithm` | `aes` / `sm4` | `aes` | 数据加密算法 |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高，AntPath） |\n\n**响应加密**（prefix: `guardian.encrypt`）\n\n| YAML Key | 类型 | 默认值 | 说明 |\n|----------|------|--------|------|\n| `enabled` | `boolean` | `true` | 总开关 |\n| `result-advice-order` | `int` | `300` | 返回值加密 Advice 排序 |\n| `key.public-key` | `String` | `\"\"` | 公钥（Base64 格式，用于加密密钥） |\n| `data.param-alias` | `String` | `encryptParam` | param 数据加密后别名 |\n| `data.body-alias` | `String` | `encryptBody` | body 数据加密后别名 |\n| `data.data-key-header` | `String` | `X-Encrypt-Key` | 数据密钥响应头名称 |\n| `data.key-mode` | `dynamic` / `static` | `dynamic` | 密钥模式：动态生成 / 静态配置 |\n| `data.key` | `String` | `\"\"` | 静态密钥（key-mode=static 时使用） |\n| `urls` | `List` | `[]` | 加密规则列表，每项参数如下 |\n| `urls[].pattern` | `String` | — | 接口路径（AntPath） |\n| `urls[].key-algorithm` | `rsa` / `sm2` | `rsa` | 密钥加密算法 |\n| `urls[].data-algorithm` | `aes` / `sm4` | `aes` | 数据加密算法 |\n| `exclude-urls` | `List\u003cString\u003e` | `[]` | 排除规则（白名单，优先级最高，AntPath） |\n\n### 使用方式\n\n以 Nacos 为例，引入 Spring Cloud Alibaba Nacos Config 依赖后，在 Nacos 控制台修改 Guardian 相关配置并发布，应用会自动感知变更并即时生效。\n\n**1. 添加依赖（以 Spring Boot 2.7.x 为例）：**\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.alibaba.cloud\u003c/groupId\u003e\n    \u003cartifactId\u003espring-cloud-starter-alibaba-nacos-config\u003c/artifactId\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.cloud\u003c/groupId\u003e\n    \u003cartifactId\u003espring-cloud-starter-bootstrap\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\n**2. 配置 `bootstrap.yml`：**\n\n```yaml\nspring:\n  application:\n    name: your-app\n  cloud:\n    nacos:\n      config:\n        server-addr: 127.0.0.1:8848\n        file-extension: yml\n```\n\n**3. 在 Nacos 控制台修改配置：**\n\n在对应的 Data ID（如 `your-app.yml`）中修改 Guardian 配置并发布，发布后即时生效，无需重启。以下是覆盖全模块的 Nacos 配置示例：\n\n```yaml\nguardian:\n  repeat-submit:\n    response-mode: exception\n    log-enabled: false\n    exclude-urls:\n      - /api/public/**\n    urls:\n      - pattern: /api/order/**\n        interval: 10\n        time-unit: seconds\n        key-scope: user\n        message: \"请勿重复提交\"\n      # SpEL 动态 Key 示例（v1.9.0+）\n      - pattern: /api/order/spel-body\n        interval: 10\n        key-scope: user\n        sp-el: \"#orderId\"\n      - pattern: /api/order/spel-mix\n        interval: 10\n        key-scope: user\n        sp-el: \"#userId + ':' + #body.orderId\"\n\n  rate-limit:\n    response-mode: exception\n    log-enabled: false\n    exclude-urls:\n      - /api/public/**\n    urls:\n      - pattern: /api/sms/send\n        qps: 1\n        window: 60\n        window-unit: seconds\n        rate-limit-scope: ip\n      - pattern: /api/seckill/**\n        qps: 10\n        capacity: 50\n        algorithm: token_bucket\n        rate-limit-scope: global\n        message: \"抢购太火爆，请稍后重试\"\n      # SpEL 动态 Key 示例（v1.9.0+）\n      - pattern: /api/product/spel-body\n        qps: 5\n        rate-limit-scope: global\n        sp-el: \"#productId\"\n      - pattern: /api/order/spel-mix\n        qps: 5\n        rate-limit-scope: global\n        sp-el: \"#userId + ':' + #body.productId\"\n\n  idempotent:\n    timeout: 300\n    time-unit: seconds\n    response-mode: exception\n    log-enabled: false\n    missing-token-message: guardian.idempotent.missing-token\n\n  auto-trim:\n    exclude-fields:\n      - password\n      - signature\n    character-replacements:\n      - from: \"\\\\u200B\"\n        to: \"\"\n      - from: \"\\\\uFEFF\"\n        to: \"\"\n\n  slow-api:\n    threshold: 3000\n    exclude-urls:\n      - /api/health\n\n  trace:\n    header-name: X-Trace-Id\n\n  ip-filter:\n    enabled: true\n    response-mode: json\n    log-enabled: true\n    message: \"IP 访问被拒绝\"\n    black-list:\n      - 192.168.100.100\n    urls:\n      - pattern: /admin/**\n        white-list:\n          - 127.0.0.1\n          - 192.168.1.*\n\n  anti-replay:\n    max-age: 60\n    nonce-ttl: 86400\n    response-mode: json\n    log-enabled: true\n    missing-timestamp-message: \"缺少时间戳\"\n    missing-nonce-message: \"缺少请求标识\"\n    expired-message: \"请求已过期\"\n    replay-message: \"重复请求\"\n    urls:\n      - pattern: /api/payment/**\n      - pattern: /api/transfer/**\n    exclude-urls:\n      - /api/public/**\n\n  api-switch:\n    response-mode: json\n    message: \"接口暂时关闭，请稍后再试\"\n    log-enabled: true\n    disabled-urls:\n      - /api-switch/disabled\n\n  sign:\n    secret-key: your-secret-key\n    response-mode: exception\n    log-enabled: false\n    sign-header: X-Sign\n    timestamp-header: X-Sign-Timestamp\n    max-age: 60\n    max-age-unit: seconds\n    missing-timestamp-message: \"缺少时间戳\"\n    missing-sign-message: \"缺少参数签名\"\n    expired-message: \"请求已过期\"\n    urls:\n      - pattern: /api/payment/**\n        algorithm: hmac_sha256\n        sign-verify-message: \"签名校验失败\"\n      - pattern: /api/order/**\n        algorithm: md5\n        sign-verify-message: \"签名校验失败\"\n\n  decrypt:\n    enabled: true\n    filter-order: -40000\n    log-enabled: false\n    response-mode: exception\n    key:\n      private-key: \"your-private-key-base64\"\n    data:\n      param-alias: \"encryptParam\"\n      body-alias: \"encryptBody\"\n      data-key-header: \"X-Encrypt-Key\"\n    missing-data-key-header-message: \"缺少数据密钥请求头\"\n    urls:\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa\n        data-algorithm: aes\n      - pattern: /api/government/**\n        key-algorithm: sm2\n        data-algorithm: sm4\n    exclude-urls:\n      - /api/public/**\n\n  encrypt:\n    enabled: true\n    result-advice-order: 300\n    key:\n      public-key: \"your-public-key-base64\"\n    data:\n      param-alias: \"encryptParam\"\n      body-alias: \"encryptBody\"\n      data-key-header: \"X-Encrypt-Key\"\n      key-mode: dynamic\n      key: \"\"\n    urls:\n      - pattern: /api/sensitive/**\n        key-algorithm: rsa\n        data-algorithm: aes\n      - pattern: /api/government/**\n        key-algorithm: sm2\n        data-algorithm: sm4\n    exclude-urls:\n      - /api/public/**\n```\n\n\u003e 只需配置你用到的模块，没用到的模块无需配置。修改任意参数后点击发布，下一次请求即可读取到最新值。\n\n### 实现原理\n\nGuardian 的 `@ConfigurationProperties` 属性类实现了模块配置接口（如 `RepeatSubmitConfig`、`RateLimitConfig` 等），拦截器/过滤器通过接口引用动态读取配置值。当配置中心推送变更时，Spring Cloud 的 `ConfigurationPropertiesRebinder` 自动重新绑定属性，所有引用该配置的组件在下次请求时即可读取到最新值。\n\n---\n\n## 消息国际化\n\nGuardian 的拒绝响应消息（防重、限流、幂等）支持 Spring 标准的 `MessageSource` 国际化机制。**不使用国际化的用户无需任何改动**，message 配置中文纯文本即可，行为与之前完全一致。\n\n### 工作原理\n\nmessage 字段同时支持**纯文本**和 **i18n Key** 两种写法：\n\n```yaml\n# 纯文本（默认，不走国际化）\nmessage: \"请求过于频繁，请稍后再试\"\n\n# i18n Key（自动走 MessageSource 解析）\nmessage: guardian.rate-limit.rejected\n```\n\nGuardian 通过 `GuardianMessageResolver` 统一解析：尝试从 `MessageSource` 查找，找到则返回对应语言的翻译，找不到则原样返回。语言由请求头 `Accept-Language` 自动决定。\n\n### 使用方式\n\n**第一步**，把 message 改成 i18n Key。\n\n**YAML 规则示例：**\n\n```yaml\nguardian:\n  rate-limit:\n    urls:\n      - pattern: /api/sms/send\n        qps: 1\n        message: guardian.rate-limit.rejected\n  repeat-submit:\n    urls:\n      - pattern: /api/order/submit\n        interval: 10\n        message: guardian.repeat-submit.rejected\n  idempotent:\n    missing-token-message: guardian.idempotent.missing-token\n```\n\n**注解示例：**\n\n```java\n@RateLimit(qps = 5, message = \"guardian.rate-limit.rejected\")\n@RepeatSubmit(interval = 10, message = \"guardian.repeat-submit.rejected\")\n@Idempotent(value = \"createOrder\", message = \"guardian.idempotent.rejected\")\n```\n\n**第二步**，在项目中添加多语言消息文件。\n\n\u003e **重要**：必须创建基础文件 `messages.properties`，Spring Boot 的 `MessageSourceAutoConfiguration` 需要检测到该文件才会激活 `ResourceBundleMessageSource`，否则国际化不生效。\n\n```properties\n# src/main/resources/messages.properties（必须，作为默认回退）\nguardian.rate-limit.rejected=请求过于频繁，请稍后再试\nguardian.repeat-submit.rejected=您的请求过于频繁，请稍后再试\nguardian.idempotent.rejected=幂等Token无效或已消费\nguardian.idempotent.missing-token=请求缺少幂等Token\n```\n\n```properties\n# src/main/resources/messages_zh_CN.properties\nguardian.rate-limit.rejected=请求过于频繁，请稍后再试\nguardian.repeat-submit.rejected=您的请求过于频繁，请稍后再试\nguardian.idempotent.rejected=幂等Token无效或已消费\nguardian.idempotent.missing-token=请求缺少幂等Token\n```\n\n```properties\n# src/main/resources/messages_en.properties\nguardian.rate-limit.rejected=Rate limit exceeded, please try again later\nguardian.repeat-submit.rejected=Too many requests, please try again later\nguardian.idempotent.rejected=Idempotent token is invalid or already consumed\nguardian.idempotent.missing-token=Missing idempotent token\n```\n\n**第三步**，通过请求头 `Accept-Language` 切换语言：\n\n| 请求头 | 匹配文件 | 效果 |\n|-------|---------|------|\n| `Accept-Language: zh-CN` | `messages_zh_CN.properties` | 中文 |\n| `Accept-Language: en` | `messages_en.properties` | 英文 |\n| 不传 | `messages.properties` | 默认回退 |\n\nSpring Boot 自动根据 `Accept-Language` 匹配语言，无需额外配置。\n\n\u003e 如果项目使用自定义消息文件路径（如 `i18n/messages`），只需配置 `spring.messages.basename=i18n/messages`，Guardian 自动适配。同样需要确保基础文件 `i18n/messages.properties` 存在。\n\n---\n\n## 架构\n\n### 模块结构\n\n```\nguardian-parent\n├── guardian-core                          # 公共基础（共享类）\n├── guardian-repeat-submit/                # 防重复提交\n│   ├── guardian-repeat-submit-core/\n│   └── guardian-repeat-submit-spring-boot-starter/\n├── guardian-rate-limit/                   # 接口限流\n│   ├── guardian-rate-limit-core/\n│   └── guardian-rate-limit-spring-boot-starter/\n├── guardian-idempotent/                   # 接口幂等\n│   ├── guardian-idempotent-core/\n│   └── guardian-idempotent-spring-boot-starter/\n├── guardian-auto-trim/                    # 参数自动Trim\n│   ├── guardian-auto-trim-core/\n│   └── guardian-auto-trim-spring-boot-starter/\n├── guardian-slow-api/                     # 慢接口检测\n│   ├── guardian-slow-api-core/\n│   └── guardian-slow-api-spring-boot-starter/\n├── guardian-trace/                        # 请求链路追踪\n│   ├── guardian-trace-core/\n│   ├── guardian-trace-spring-boot-starter/\n│   ├── guardian-trace-rabbitmq/           # RabbitMQ 链路追踪\n│   ├── guardian-trace-kafka/              # Kafka 链路追踪\n│   └── guardian-trace-rocketmq/           # RocketMQ 链路追踪\n├── guardian-ip-filter/                    # IP黑白名单\n│   ├── guardian-ip-filter-core/\n│   └── guardian-ip-filter-spring-boot-starter/\n├── guardian-anti-replay/                  # 防重放攻击\n│   ├── guardian-anti-replay-core/\n│   └── guardian-anti-replay-spring-boot-starter/\n├── guardian-api-switch/                  # 接口开关\n│   ├── guardian-api-switch-core/\n│   └── guardian-api-switch-spring-boot-starter/\n├── guardian-sign/                         # 参数签名\n│   ├── guardian-sign-core/\n│   └── guardian-sign-spring-boot-starter/\n├── guardian-encrypt/                      # 请求加解密\n│   ├── guardian-encrypt-core/\n│   └── guardian-encrypt-spring-boot-starter/\n├── guardian-starter-all/                  # 整合引用所有Starter模块\n├── guardian-storage-redis/                # Redis 存储（多模块共享）\n└── guardian-example/                      # 示例工程\n```\n\n### 执行顺序\n\nGuardian 的 Filter 和 Interceptor 通过 `order` 值控制执行优先级，**值越小越先执行**。\n\n#### Filter 执行顺序\n\nFilter 在 Servlet 层执行，先于所有 Interceptor：\n\n| 顺序 | 模块 | 配置项 | 默认 order | 说明 |\n|------|------|--------|-----------|------|\n| 1 | 请求体缓存 | `guardian.repeatable-filter-order` | **-100000** | 最先执行，缓存请求体供后续模块重复读取 |\n| 2 | 请求解密 | `guardian.decrypt.filter-order` | **-40000** | 解密请求数据 |\n| 3 | 请求链路追踪 | `guardian.trace.filter-order` | **-30000** | 为后续所有操作提供 TraceId |\n| 4 | IP 黑白名单 | `guardian.ip-filter.filter-order` | **-20000** | 拦截恶意 IP，尽早阻断 |\n| 5 | 防重放攻击 | `guardian.anti-replay.filter-order` | **-14000** | 校验 Timestamp + Nonce，拦截重放请求 |\n| 6 | 参数自动 Trim | `guardian.auto-trim.filter-order` | **-10000** | 参数预处理，在业务逻辑前清洗数据 |\n\n#### Interceptor 执行顺序\n\nInterceptor 在 Spring MVC 层执行，Filter 之后：\n\n| 顺序 | 模块    | 配置项 | 默认 order  | 说明                          |\n|----|-------|--------|-----------|-----------------------------|\n| 1  | 慢接口检测 | `guardian.slow-api.interceptor-order` | **-1000** | 最先进入最后退出，精确计算整体耗时           |\n| 2  | 接口开关  | `guardian.api-switch.interceptor-order` | **500**   | 接口关闭直接拒绝，避免后续无意义计算          |\n| 3  | 接口限流  | `guardian.rate-limit.interceptor-order` | **1000**  | 流量超限直接拒绝，避免后续无意义计算          |\n| 4  | 防重复提交 | `guardian.repeat-submit.interceptor-order` | **2000**  | 通过限流后，判断是否短时间重复请求           |\n| 5  | 接口幂等  | `guardian.idempotent.interceptor-order` | **3000**  | 消费 Token 不可逆，确保前面校验都通过 |\n| 6  | 参数签名  | `guardian.sign.interceptor-order` | **4000** | 签名验证，确保请求参数未被篡改            |\n\n每个模块的 order 均可通过 YAML 自定义，方便与项目中其他拦截器协调：\n\n```yaml\nguardian:\n  # Filter 排序\n  repeatable-filter-order: -100000       # 请求体缓存（最先执行，全局共享）\n  decrypt:\n    filter-order: -40000                 # 请求解密\n  trace:\n    filter-order: -30000                 # 链路追踪\n  ip-filter:\n    filter-order: -20000                 # IP 黑白名单\n  anti-replay:\n    filter-order: -14000                 # 防重放攻击\n  auto-trim:\n    filter-order: -10000                 # 参数自动 Trim\n\n  # Interceptor 排序\n  slow-api:\n    interceptor-order: -1000             # 慢接口检测\n  api-switch:\n    interceptor-order: 500               # 接口开关\n  rate-limit:\n    interceptor-order: 1000              # 接口限流\n  repeat-submit:\n    interceptor-order: 2000              # 防重复提交\n  idempotent:\n    interceptor-order: 3000              # 接口幂等\n  sign:\n    interceptor-order: 4000              # 参数签名\n\n  # ResponseBodyAdvice 排序\n  idempotent:\n    result-advice-order: 200             # 幂等结果缓存（需开启 result-cache）\n  sign:\n    result-advice-order: 100             # 响应结果签名（需开启 result-sign）\n  encrypt:\n    result-advice-order: 300             # 响应结果加密\n```\n\n## 存储对比\n\n| | Redis | Local |\n|--|-------|-------|\n| 分布式 | 支持 | 仅单机 |\n| 持久性 | Redis 持久化 | 重启丢失 |\n| 推荐场景 | 生产环境 | 开发/单体应用 |\n| 额外依赖 | 需要 Redis | 无 |\n\n## 更新日志\n\n### v1.10.0\n\n- **新增**：请求加解密模块（`guardian-encrypt`），支持 RSA+AES / SM2+SM4 加密方案\n- **新增**：请求解密过滤器（`DecryptFilter`），自动解密请求参数\n- **新增**：响应加密 Advice（`EncryptResponseAdvice`），自动加密响应结果\n- **新增**：密钥加密服务（`KeyEncryptService`），支持 RSA 和 SM2 算法\n- **新增**：数据加密服务（`DataEncryptService`），支持 AES-GCM 和 SM4-CBC 算法\n- **新增**：加密模块 Actuator 端点（`GET /actuator/guardianEncrypt`）\n- **新增**：前端示例页面（`sign-test.html`、`encrypt-test.html`），提供完整的前端实现参考\n- **新增**：签名模块支持 SM3 算法\n- **优化**：签名模块前端实现文档简化，指向示例页面\n- **优化**：加密模块支持动态密钥和静态密钥两种模式\n\n### v1.9.0\n\n- **新增**：防重复提交模块支持 SpEL 表达式动态生成 Key（`spEl` 属性）\n- **新增**：接口限流模块支持 SpEL 表达式动态生成 Key（`spEl` 属性）\n- **新增**：`SpElUtils` 工具类，支持 `#参数名` 和 `#body.参数名` 访问请求参数\n\n### v1.8.1\n\n- **新增**：ResponseBodyAdvice 执行顺序可配置，通过 YAML 动态控制\n- **新增**：签名模块 `result-advice-order` 配置项（默认100）\n- **新增**：幂等模块 `result-advice-order` 配置项（默认200）\n- **修复**：修复 `IdempotentResultCacheAdvice` 未添加 `@ControllerAdvice` 注解导致 Advice 不生效的问题\n- **修复**：修复 `SignResultSignAdvice` Advice 执行顺序不可控的问题\n- **优化**：完善拦截器与 Advice 执行顺序说明\n\n### v1.8.0\n\n- **新增**：参数签名模块（`guardian-sign`），支持多种签名算法，请求参数签名验证 + 响应结果签名\n- **新增**：`@SignVerify` 注解，支持指定签名算法和自定义错误信息\n- **新增**：签名验证拦截器（`SignVerifyInterceptor`），自动校验请求签名\n- **新增**：响应结果签名（`SignResultSignAdvice`），确保返回数据的完整性\n- **新增**：签名服务（`SignService`），支持 BASE64、MD5、SHA256 和 HMAC-SHA256 四种算法\n- **新增**：签名统计和 Actuator 端点（`GET /actuator/guardianSign`）\n- **修复**：HMAC-SHA256 算法实现错误，添加了密钥参数\n- **修复**：配置验证，添加了对 secretKey 的非空检查\n- **修复**：响应签名空值检查缺失问题\n\n### v1.7.2\n\n- **新增**：接口开关模块（`guardian-api-switch`），动态关闭/开启接口\n\n### v1.7.1\n\n- **新增**：整合所有starter模块（`guardian-starter-all`），以便于引用所有功能时只需引用此模块\n- **优化**：共享类重新分类分包\n\n### v1.7.0\n\n- **新增**：防重放攻击模块（`guardian-anti-replay-spring-boot-starter`），通过 Timestamp + Nonce 双重校验拦截重放请求\n- **新增**：`NonceStorage` 存储接口，支持 Redis（分布式）和 Local（单机）两种实现\n- **新增**：`nonce-ttl` 与 `max-age` 解耦设计，Nonce 存活时间（默认 24h）远大于 Timestamp 有效窗口（默认 60s），防止攻击者在 Nonce 过期后篡改 Timestamp 重放\n- **新增**：`nonce-ttl-unit` / `max-age-unit` 支持自定义时间单位，`validate()` 方法统一换算后比较\n- **新增**：防重放攻击 Actuator 监控端点（`GET /actuator/guardianAntiReplay`），展示拦截统计和 Top N 被拦截接口\n- **新增**：防重放攻击拦截日志（`log-enabled: true`，前缀 `[Guardian-Anti-Replay]`）\n- **新增**：防重放攻击 URL 规则 + 排除规则（`urls` + `exclude-urls`），支持 AntPath 匹配\n- **新增**：`AntiReplayResponseHandler` SPI 扩展点，支持自定义拒绝响应格式\n- **新增**：全部拒绝提示信息支持 i18n Key（`missing-timestamp-message` / `missing-nonce-message` / `expired-message` / `replay-message`）\n\n### v1.6.2\n\n- **新增**：MQ 消息链路追踪，支持 RabbitMQ、Kafka、RocketMQ 三种消息队列的 TraceId 自动传递\n- **新增**：`guardian-trace-rabbitmq` 模块，发送端通过 MessagePostProcessor 注入 traceId，消费端通过 AOP 切面拦截 `@RabbitListener` 自动提取\n- **新增**：`guardian-trace-kafka` 模块，发送端通过 ProducerInterceptor 注入 traceId，消费端通过 AOP 切面拦截 `@KafkaListener` 自动提取\n- **新增**：`guardian-trace-rocketmq` 模块，发送端通过 SendMessageHook 注入 traceId，消费端通过 AOP 切面拦截 `@RocketMQMessageListener` 自动提取\n- **新增**：`TraceUtils.switchTraceId()` 方法，支持 MQ 批量消费场景逐条切换 traceId\n- **新增**：`TraceRabbitUtils` / `TraceKafkaUtils` / `TraceRocketMQUtils` 批量消费专用工具类，一行代码从消息中提取并切换 traceId\n- **新增**：慢接口记录器 SPI 扩展点（`SlowApiRecorder`），支持自定义持久化方案（数据库 / ES 等）替换默认内存实现\n- **优化**：MQ 消费端统一采用 AOP 切面拦截，不占用框架原生拦截器/Hook 位置，与用户自定义拦截器互不冲突\n- **优化**：Kafka ProducerInterceptor 通过持有 TraceConfig 引用动态获取 headerName，支持配置中心热更新\n- **修复**：MQ 自动配置类添加 `@AutoConfigureAfter`，确保在 `TraceConfig` 和 MQ Template Bean 创建之后加载，解决因加载顺序导致的链路追踪不生效问题\n\n### v1.6.1\n\n- **重构**：移除 `hutool-all` 依赖，全面切换 Spring/JDK/Jackson 原生工具，项目零外部工具包依赖\n- **新增**：`GuardianJsonUtils`（基于 Jackson）、`IpUtils`、`Md5Utils` 三个核心工具类\n- **修复**：`jackson-databind` 作用域由 `provided` 改为 `compile`，解决下游模块编译失败\n\n### v1.6.0\n\n- **新增**：IP 黑白名单模块（`guardian-ip-filter-spring-boot-starter`），支持全局 IP 黑名单 + URL 绑定白名单\n- **新增**：IP 规则匹配支持精确匹配、通配符（`192.168.1.*`）和 CIDR（`10.0.0.0/8`）三种格式\n- **新增**：IP 黑白名单拦截统计 + Actuator 端点（`GET /actuator/guardianIpFilter`）\n- **新增**：`IpMatcher` 工具类，统一处理 IP 规则匹配逻辑\n- **新增**：IP 黑白名单拦截日志（`log-enabled: true`，前缀 `[Guardian-Ip-Filter]`）\n\n### v1.5.3\n\n- **新增**：消息国际化支持，拒绝响应消息（防重、限流、幂等）支持 Spring `MessageSource` 国际化，message 字段可配置 i18n Key，根据 `Accept-Language` 自动匹配语言\n- **新增**：`GuardianMessageResolver` 消息解析工具，MessageSource 能解析则返回翻译，否则原样返回，不使用国际化的用户零感知\n- **新增**：幂等模块 `missing-token-message` 配置项，缺少 Token 时的提示信息支持自定义及国际化\n\n### v1.5.2\n\n- **优化**：`GuardianRateLimitProperties` / `GuardianRepeatSubmitProperties` 删除多余无用参数\n\n### v1.5.1\n\n- **新增**：全模块配置动态刷新支持，配合 Nacos / Apollo 等配置中心可在不重启应用的情况下实时更新 Guardian 配置\n- **新增**：`BaseConfig` / `BaseCharacterReplacement` 配置接口，拦截器/过滤器通过接口引用动态读取配置，与 Spring Cloud `ConfigurationPropertiesRebinder` 无缝集成\n- **优化**：`CharacterSanitizer` 引入基于哈希值的缓存机制，配置未变更时零解析开销，配置动态刷新后自动重建\n- **修复**：`DefaultIdempotentTokenService` 改为持有 `IdempotentConfig` 接口引用，修复 Token 过期时间无法动态更新的问题\n\n### v1.5.0\n\n- **新增**：参数自动Trim模块（`guardian-auto-trim-spring-boot-starter`），自动去除请求参数首尾空格，支持表单参数 + JSON Body\n- **新增**：不可见字符替换功能（`character-replacements`），可清除零宽空格、BOM、回车符等从复制粘贴混入的不可见字符\n- **新增**：排除字段配置（`exclude-fields`），密码、签名等敏感字段可跳过 Trim\n- **新增**：慢接口检测模块（`guardian-slow-api-spring-boot-starter`），超过阈值自动记录并打印 WARN 日志\n- **新增**：`@SlowApiThreshold` 注解，支持为单个接口自定义慢接口阈值\n- **新增**：慢接口 Actuator 端点（`GET /actuator/guardianSlowApi`），展示触发次数 + Top N 排行 + 最大耗时\n- **新增**：请求链路追踪模块（`guardian-trace-spring-boot-starter`），自动生成/透传 TraceId\n- **新增**：TraceId 写入 MDC + 响应头，Logback pattern 加 `%X{traceId}` 即可串联全链路日志\n- **新增**：上游服务通过 `X-Trace-Id` 请求头透传 TraceId，支持跨服务链路追踪\n\n### v1.4.3\n\n- **新增**：接口幂等模块（`guardian-idempotent-spring-boot-starter`），Token 机制保证接口幂等性\n- **新增**：幂等结果缓存，开启后重复请求返回首次执行结果\n- **新增**：幂等 Actuator 端点、拦截统计、Token 生成器可插拔\n- **新增**：幂等 PARAM 模式支持从 JSON Body 解析 Token（兼容 form-data / x-www-form-urlencoded / JSON 三种 POST 传参）\n- **优化**：`RepeatableRequestFilter` 排序提升到全局配置 `GuardianCoreProperties`（`guardian.repeatable-filter-order`），仅需配置一次\n- **优化**：拦截器执行顺序可配置（`interceptor-order`），默认：限流 1000 → 防重 2000 → 幂等 3000\n- **优化**：三模块 Properties 提取公共基类 `BaseGuardianProperties`，统一 storage / responseMode / logEnabled / interceptorOrder\n- **优化**：移除 Manager 中间层（`KeyGeneratorManager`、`RateLimitKeyGeneratorManager`、`KeyEncryptManager`、`IdempotentTokenGeneratorManager`），改为构造函数直接注入\n- **优化**：删除未使用的异常类（`TokenGeneratorNotFoundException`、`KeyGeneratorNotFoundException`、`KeyEncryptNotFoundException`）\n- **修复**：幂等 null 返回值处理，与 Spring 原生行为保持一致\n- **修复**：`BaseResult.error()` 状态码错误（200 → 500）\n- **修复**：Actuator Endpoint ID 改为驼峰命名，消除 Spring Boot 启动 WARN\n- **修复**：三模块参数校验（`qps`、`window`、`interval`、`timeout` ≤ 0 时抛出明确异常）\n- **修复**：令牌桶算法时间回拨防护（Redis Lua + 本地存储均加 `max(0, elapsed)`）\n\n### v1.3.0\n\n- **新增**：接口限流令牌桶算法（`TOKEN_BUCKET`），支持突发流量\n- **修复**：本地滑动窗口并发竞态条件（check-then-act），加 synchronized 保证原子性\n- **修复**：本地缓存内存泄漏，增加守护线程定期清理过期 Key\n- **修复**：rate-limit 模块 POM parent 版本写死，统一改为 `${revision}`\n\n### v1.2.0\n\n- **新增**：接口限流模块（滑动窗口算法），支持注解 + YAML 双模式\n- **新增**：限流维度（GLOBAL / IP / USER）\n- **新增**：限流 Actuator 监控端点\n\n### v1.1.0\n\n- 防重复提交初始版本\n\n## 环境要求\n\n- **JDK** 1.8+\n- **Spring Boot** 2.7.x\n- **Redis** 5.0+（使用 Redis 存储时）\n\n## 仓库地址\n\n| 平台 | 地址 |\n|------|------|\n| GitHub（主仓） | https://github.com/BigGG-Guardian/guardian |\n| Gitee（镜像同步） | https://gitee.com/BigGG-Guardian/guardian |\n| Maven Central | https://central.sonatype.com/artifact/io.github.biggg-guardian/guardian-repeat-submit-spring-boot-starter |\n\n\u003e Gitee 从 GitHub 自动同步，Issues 和 PR 请提交到 GitHub。\n\n## 开源协议\n\n[Apache License 2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBigGG-Guardian%2Fguardian","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FBigGG-Guardian%2Fguardian","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBigGG-Guardian%2Fguardian/lists"}