# yyc-common-gray

基于 Nacos 的**版本灰度**与**跨 Group 服务发现**公共组件，供 Feign + Ribbon 消费者与 Spring Cloud Gateway 复用。

## 能力概览

| 能力 | 场景 | 核心类 |
|------|------|--------|
| 版本灰度（Ribbon） | 微服务通过 Feign 调用同 Group 服务 | `GrayRibbonLoadBalancerRule`、`GrayFeignRequestInterceptor` |
| 跨 Group 发现（Ribbon） | 消费者与提供方不在同一 Nacos Group | `NacosCrossGroupServerList` |
| 跨 Group 工具（Gateway） | 路由 URI 编解码、直接查 Nacos 选实例 | `NacosGroupServiceIdCodec`、`NacosCrossGroupInstanceSelector` |

引入依赖后通过 `META-INF/spring.factories` 自动装配，无需手动 `@Import`。

## 依赖引入

```xml
<dependency>
    <groupId>net.yyc.common</groupId>
    <artifactId>yyc-common-gray</artifactId>
    <version>${yyc-common.version}</version>
</dependency>
```

传递依赖：`yyc-common-core`、`spring-cloud-starter-alibaba-nacos-discovery`、`feign-core`。

适用栈：Spring Cloud Alibaba **2.2.x** + Netflix Ribbon（`@FeignClient` + `ribbon` 负载均衡）。

## 版本灰度（Feign / Ribbon）

### 开关

```yaml
gray:
  rule:
    enabled: true
```

开启后注册：

- `ribbonLoadBalancerRule` → `GrayRibbonLoadBalancerRule`：按请求头 `VERSION` 与实例元数据 `VERSION` 匹配；无头或未匹配时随机选实例。
- `grayFeignRequestInterceptor`：将当前 HTTP 请求的 `VERSION` 头透传到下游 Feign 调用。

### 约定

- 请求头 / 元数据键名：`VERSION`（见 `CommonConstants.VERSION`）。
- 实例需在 Nacos 注册元数据中携带对应 `VERSION` 字段。

### 注意

- 非 Web 场景（定时任务、消息消费等）无 `HttpServletRequest` 时，规则会退化为随机选实例，与改造前行为一致。
- **不要**再配置 `NFLoadBalancerRuleClassName`（如 `NacosRule`），会与 `ribbonLoadBalancerRule` Bean 冲突。

## 跨 Nacos Group（Feign / Ribbon）

官方 `NacosServerList` 固定使用消费者自身的 `spring.cloud.nacos.discovery.group`，无法通过 `{serviceId}.ribbon.nacos.group` 指定目标 Group。本模块用 `NacosCrossGroupServerList` 覆盖实例拉取逻辑。

### 配置

为每个 `@FeignClient` 的 `value`（即 Ribbon `clientName` / `serviceId`）指定目标 Group：

```yaml
yyc:
  nacos:
    cross-group:
      gulop-gig-biz: cso_gig
```

未配置时 fallback 到 `spring.cloud.nacos.discovery.group`，与同 Group 服务行为一致。

### 示例

消费者 `hnqz-upms-biz`（Group `cso_hnqz`）调用 `gulop-gig-biz`（Group `cso_gig`）：

```java
@FeignClient(value = "gulop-gig-biz")
public interface GigUserApiClient { ... }
```

```yaml
spring:
  cloud:
    nacos:
      discovery:
        group: cso_hnqz

yyc:
  nacos:
    cross-group:
      gulop-gig-biz: cso_gig

gray:
  rule:
    enabled: true
```

### 自动配置说明

| 组件 | 作用 |
|------|------|
| `NacosCrossGroupEnvironmentPostProcessor` | 启动早期排除 `RibbonNacosAutoConfiguration`，避免与自定义 `@RibbonClients` 重复注册 |
| `NacosCrossGroupRibbonAutoConfiguration` | `@RibbonClients(defaultConfiguration = NacosCrossGroupRibbonClientConfiguration.class)` |
| `NacosCrossGroupRibbonClientConfiguration` | 注册 `NacosCrossGroupServerList` 替代默认 `NacosServerList` |

激活条件：`@ConditionalOnRibbonNacos`（项目启用 Ribbon + Nacos 时）。

## 跨 Nacos Group（Gateway 共享工具）

网关侧不经过 Ribbon，使用 `net.yyc.common.gray.nacos` 包中的工具类（`yyc-gateway-biz` 等业务模块引用本 jar 即可）。

### 服务名编解码

Java `URI` 的 host 不允许 `@`、`_` 等字符，跨 Group 路由使用：

| 形态 | 示例 |
|------|------|
| Nacos serviceId | `cso_gig@@gulop-gig-biz` |
| 路由 URI host（存储） | `cso-gig--gulop-gig-biz`（group 中 `_` → `-`） |

`NacosGroupServiceIdCodec` 提供 `encodeLbUri`、`parseFromUriHost`、`parseFromNacosServiceId` 等方法，并处理 `%40%40` 被 URI 解析器编码的情况。

### 实例选择

存在 `org.springframework.cloud.gateway.filter.GlobalFilter` 时，自动注册 `NacosCrossGroupInstanceSelector` Bean，按 **group + serviceName** 调用 Nacos `selectInstances`，并支持 `VERSION` 头灰度（与 Ribbon 规则语义一致）。

业务侧（如 `NacosGroupGrayLoadBalancer`）注入该 Bean 并传入版本头即可。

## 架构关系

```mermaid
flowchart TB
  subgraph feign [Feign 消费者]
    REQ[HTTP 请求 VERSION]
    INT[GrayFeignRequestInterceptor]
    SL[NacosCrossGroupServerList]
    RULE[GrayRibbonLoadBalancerRule]
    REQ --> INT
    INT --> RULE
    SL --> RULE
    SL -->|"yyc.nacos.cross-group.{serviceId}"| Nacos[(Nacos)]
  end

  subgraph gw [Gateway]
    CODEC[NacosGroupServiceIdCodec]
    SEL[NacosCrossGroupInstanceSelector]
    CODEC --> SEL
    SEL --> Nacos
  end
```

## 包结构

```
net.yyc.common.gray
├── GrayRibbonLoadBalancerConfiguration    # gray.rule.enabled 灰度规则与 Feign 拦截器
├── feign/
│   └── GrayFeignRequestInterceptor
├── rule/
│   └── GrayRibbonLoadBalancerRule
├── ribbon/
│   ├── NacosCrossGroupEnvironmentPostProcessor
│   ├── NacosCrossGroupRibbonAutoConfiguration
│   ├── NacosCrossGroupRibbonClientConfiguration
│   └── NacosCrossGroupServerList
└── nacos/
    ├── NacosGroupService
    ├── NacosGroupServiceIdCodec
    ├── NacosCrossGroupInstanceSelector
    └── NacosCrossGroupGatewayAutoConfiguration
```

## 常见问题

**Q: 配置了 `yyc.nacos.cross-group.gulop-gig-biz` 仍拉不到实例？**  
A: 确认已引入本模块且未排除 `NacosCrossGroupRibbonAutoConfiguration`；查看日志中 `Nacos cross-group server list` 的 `targetGroup` 是否为预期值。

**Q: Gateway 需要单独配 `yyc.nacos.cross-group` 吗？**  
A: 不需要。网关跨 Group 在路由 URI 中编码 `group--service`，由 `NacosCrossGroupInstanceSelector` 解析后查询；与 Feign 的 YAML 键是两套机制。

**Q: 与 `yyc-common-gateway` 的关系？**  
A: `yyc-common-gateway` 提供 `GrayLoadBalancer`、`VersionGrayLoadBalancer` 等 Gateway 灰度接口；本模块提供 Nacos 跨 Group 底层能力与 Ribbon 侧完整链路。网关业务模块通常同时依赖两者。

## 构建

```bash
cd yyc-common-parent
mvn -pl yyc-common-gray -am install
```

发布新版本后，通过 `yyc-common-bom` 统一升级各服务的 `yyc-common-gray` 坐标。
