Repository: alibaba/higress
Pull request #2011: [frontend-gray] 重构业务逻辑，对于微前端和多版本支持更加友好
Author: login: heimanba
Created: 2025-04-02T12:21:12Z | Merged: 2025-04-11T02:35:18Z

<!-- Please make sure you have read and understood the contributing guidelines -->

### Ⅰ. Describe what this PR did
重构业务逻辑，重点支持微前端的能力，按照比例灰度，以及全链路灰度串联


### Ⅱ. Does this pull request fix one issue?
- 设置一个随机的客户端的cookie 黏贴，并且通过hash，实现按照比例灰度
- 按照比例灰度的key值通过`uniqueGrayTag`可以自定义
- skippedPaths 替换 skippedPathPrefixes ，支持glob语法
- indexPaths  替换 includePathPrefixes ，支持glob语法
- cookie存储过期时间默认为1年，默认不需要过期




Diff:
diff --git a/plugins/wasm-go/extensions/frontend-gray/Makefile b/plugins/wasm-go/extensions/frontend-gray/Makefile
new file mode 100644
index 0000000000..61e6b1cbda
--- /dev/null
+++ b/plugins/wasm-go/extensions/frontend-gray/Makefile
@@ -0,0 +1,11 @@
+.PHONY: reload
+
+build:
+	tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./main.go
+
+reload:
+	tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./main.go
+	./envoy -c envoy.yaml --concurrency 0 --log-level info --component-log-level wasm:debug
+
+start:
+	./envoy -c envoy.yaml --concurrency 0 --log-level info --component-log-level wasm:debug
\ No newline at end of file
diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md
index 8d21869cfe..eea2256fdb 100644
--- a/plugins/wasm-go/extensions/frontend-gray/README.md
+++ b/plugins/wasm-go/extensions/frontend-gray/README.md
@@ -9,8 +9,8 @@ description: 前端灰度插件配置参考
 
 ## 运行属性
 
-插件执行阶段：`认证阶段`
-插件执行优先级：`450`
+插件执行阶段：`默认阶段`
+插件执行优先级：`1000`
 
 
 ## 配置字段
@@ -19,16 +19,17 @@ description: 前端灰度插件配置参考
 | `grayKey`         | string       | 非必填 | -   | 用户ID的唯一标识，可以来自Cookie或者Header中，比如 userid，如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 |
 | `localStorageGrayKey`         | string       | 非必填 | -   | 使用JWT鉴权方式，用户ID的唯一标识来自`localStorage`中，如果配置了当前参数，则`grayKey`失效 |
 | `graySubKey`    | string       | 非必填 | -   | 用户身份信息可能以JSON形式透出，比如：`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` |
-| `userStickyMaxAge`         | int       | 非必填 | 172800   | 用户粘滞的时长：单位为秒，默认为`172800`，2天时间 |
-| `includePathPrefixes` | array of strings | 非必填 | - | 强制处理的路径。例如，在  微前端 场景下，XHR 接口如： `/resource/xxx`本质是一个资源请求，需要走插件转发逻辑。 |
-| `skippedPathPrefixes` | array of strings | 非必填 | - | 用于排除特定路径，避免当前插件处理这些请求。例如，在 rewrite 场景下，XHR 接口请求 `/api/xxx` 如果经过插件转发逻辑，可能会导致非预期的结果。 |
+| `storeMaxAge`         | int       | 非必填 | 60 * 60 * 24 * 365   | 网关设置Cookie最大存储时长：单位为秒，默认为1年 |
+| `indexPaths` | array of strings | 非必填 | - | 强制处理的路径，支持 `Glob` 模式匹配。例如：在 微前端场景下，XHR 接口如： `/resource/**/manifest-main.json`本质是一个资源请求，需要走插件转发逻辑。 |
+| `skippedPaths` | array of strings | 非必填 | - | 用于排除特定路径，避免当前插件处理这些请求，支持 `Glob` 模式匹配。例如，在 rewrite 场景下，XHR 接口请求 `/api/**` 如果经过插件转发逻辑，可能会导致非预期的结果。 |
 | `skippedByHeaders` | map of string to string   | 非必填  | -   | 用于通过请求头过滤，指定哪些请求不被当前插件
-处理。`skippedPathPrefixes` 的优先级高于当前配置，且页面HTML请求不受本配置的影响。若本配置为空，默认会判断`sec-fetch-mode=cors`以及`upgrade=websocket`两个header头，进行过滤 |
+处理。`skippedPaths` 的优先级高于当前配置，且页面HTML请求不受本配置的影响。 |
 | `rules`      | array of object | 必填 | -   | 用户定义不同的灰度规则，适配不同的灰度场景   |
 | `rewrite`      | object | 必填 | -   | 重写配置，一般用于OSS/CDN前端部署的重写配置  |
 | `baseDeployment` | object   | 非必填 | -   | 配置Base基线规则的配置    |
 | `grayDeployments` |  array of object   | 非必填 | -   | 配置Gray灰度的生效规则，以及生效版本                                |
 | `backendGrayTag`     | string       | 非必填  | `x-mse-tag`   | 后端灰度版本Tag，如果配置了，cookie中将携带值为`${backendGrayTag}:${grayDeployments[].backendVersion}` |
+| `uniqueGrayTag`     | string       | 非必填  | `x-higress-uid`   | 开启按照比例灰度时候，网关会生成一个唯一标识存在`cookie`中，一方面用于session黏贴，另一方面后端也可以使用这个值用于全链路的灰度串联 |
 | `injection`     | object    | 非必填  | -   | 往首页HTML中注入全局信息，比如`<script>window.global = {...}</script>` |
 
 
@@ -50,7 +51,6 @@ description: 前端灰度插件配置参考
 | 名称         | 数据类型         | 填写要求 | 默认值 | 描述                           |
 |------------|--------------|------|-----|------------------------------|
 | `host`     | string       | 非必填  | -   | host地址，如果是OSS则设置为 VPC 内网访问地址 |
-| `notFoundUri` | string       | 非必填  | -   | 404 页面配置                     |
 | `indexRouting`    | map of string to string       | 非必填  | -   | 用于定义首页重写路由规则。每个键 (Key) 表示首页的路由路径，值 (Value) 则指向重定向的目标文件。例如，键为 `/app1` 对应的值为 `/mfe/app1/{version}/index.html`。生效version为`0.0.1`， 访问路径为 `/app1`，则重定向到 `/mfe/app1/0.0.1/index.html`。                     |
 | `fileRouting`     | map of string to string       | 非必填  | -   | 用于定义资源文件重写路由规则。每个键 (Key) 表示资源访问路径，值 (Value) 则指向重定向的目标文件。例如，键为 `/app1/` 对应的值为 `/mfe/app1/{version}`。生效version为`0.0.1`，访问路径为 `/app1/js/a.js`，则重定向到 `/mfe/app1/0.0.1/js/a.js`。                     |
 
@@ -59,6 +59,7 @@ description: 前端灰度插件配置参考
 | 名称             | 数据类型         | 填写要求 | 默认值 | 描述                                                                                |
 |----------------|--------------|------|-----|-----------------------------------------------------------------------------------|
 | `version`         | string       | 必填   | -   | Base版本的版本号，作为兜底的版本 |
+| `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `versionPredicates`  | string | 必填   | -   | 和`version`含义相同，但是满足多版本的需求：根据不同路由映射不同的`Version`版本。一般用于微前端的场景：一个主应用需要管理多个微应用 |
 
 `grayDeployments`字段配置说明：
@@ -70,17 +71,28 @@ description: 前端灰度插件配置参考
 | `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `name` | string | 必填   | -   | 规则名称和`rules[].name`关联 |
 | `enabled`  | boolean   | 必填   | -   | 是否启动当前灰度规则                                      |
-| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如`50`。注意：灰度规则权重总和不能超过100，如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
-> 为了实现按比例（weight） 进行灰度发布，并确保用户粘滞，我们需要确认客户端的唯一性。如果配置了 grayKey，则将其用作唯一标识；如果未配置 grayKey，则使用客户端的访问 IP 地址作为唯一标识。
+| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如50。 |
+>按照比例灰度注意下面几点:
+> 1. 如果同时配置了`按用户灰度`以及`按比例灰度`，按`比例灰度`优先生效
+> 2. 采用客户端设备标识符的哈希摘要机制实现流量比例控制，其唯一性判定逻辑遵循以下原则：自动生成全局唯一标识符（UUID）作为设备指纹，可以通过`uniqueGrayTag`配置`cookie`的key值，并通过SHA-256哈希算法生成对应灰度判定基准值。
 
 
 `injection`字段配置说明：
 
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
+| `globalConfig`  | object | 非必填   | -   | 注入到HTML首页的全局变量 |
 | `head`  | array of string | 非必填   | -   | 注入head信息，比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
 | `body`  | object | 非必填   | -   | 注入Body |
 
+`injection.globalConfig`字段配置说明：
+| 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
+|--------|--------|------|-----|-------------------------------------------------|
+| `key`  | string | 非必填   |  HIGRESS_CONSOLE_CONFIG  | 注入到window全局变量的key值 |
+| `featureKey`  | string | 非必填   | FEATURE_STATUS   | 关于`rules`相关规则的命中情况，返回实例`{"beta-user":true,"inner-user":false}` |
+| `value`  | string | 非必填   | -   | 自定义的全局变量 |
+| `enabled`  | boolean | 非必填   | false   | 是否开启注入全局变量 |
+
 `injection.body`字段配置说明：
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
@@ -139,8 +151,7 @@ grayDeployments:
     enabled: true
     weight: 80
 ```
-总的灰度规则为100%，其中灰度版本的权重为`80%`，基线版本为`20%`。一旦用户命中了灰度规则，会根据IP固定这个用户的灰度版本（否则会在下次请求时随机选择一个灰度版本）。
-
+总的灰度规则为100%，其中灰度版本的权重为80%，基线版本为20%。
 ### 用户信息存在JSON中
 
 ```yml
@@ -218,7 +229,6 @@ rules:
   - level5
 rewrite:
   host: frontend-gray.oss-cn-shanghai-internal.aliyuncs.com
-  notFoundUri: /mfe/app1/dev/404.html
   indexRouting:
     /app1: '/mfe/app1/{version}/index.html'
     /: '/mfe/app1/{version}/index.html',
@@ -260,7 +270,6 @@ grayDeployments:
   - name: beta-user
     version: gray
     enabled: true
-    weight: 80
 injection:
   head: 
     - <script>console.log('Header')</script>
diff --git a/plugins/wasm-go/extensions/frontend-gray/README_EN.md b/plugins/wasm-go/extensions/frontend-gray/README_EN.md
index b3a652d449..76c73740d7 100644
--- a/plugins/wasm-go/extensions/frontend-gray/README_EN.md
+++ b/plugins/wasm-go/extensions/frontend-gray/README_EN.md
@@ -1,122 +1,95 @@
 ---
 title: Frontend Gray
 keywords: [higress, frontend gray]
-description: Frontend gray plugin configuration reference
----
-## Function Description
-The `frontend-gray` plugin implements the functionality of user gray release on the frontend. Through this plugin, it can be used for business `A/B testing`, while the `gradual release` combined with `monitorable` and `rollback` strategies ensures the stability of system release operations.
+description: Frontend Gray Plugin Configuration Reference
+
+## Feature Description
+The `frontend-gray` plugin implements frontend user grayscale capabilities. This plugin can be used for business `A/B testing` while ensuring system release stability through `grayscale`, `monitoring`, and `rollback` strategies.
+
+## Runtime Properties
 
-## Runtime Attributes
-Plugin execution phase: `Authentication Phase`  
-Plugin execution priority: `450`
+Execution Stage: `Default Stage`  
+Execution Priority: `1000`
 
 ## Configuration Fields
-| Name             | Data Type         | Requirements  | Default Value | Description                                                                                                 |
-|-----------------|-------------------|---------------|---------------|-------------------------------------------------------------------------------------------------------------|
-| `grayKey`       | string            | Optional      | -             | The unique identifier of the user ID, which can be from Cookie or Header, such as userid. If not provided, uses `rules[].grayTagKey` and `rules[].grayTagValue` to filter gray release rules. |
-| `graySubKey`    | string            | Optional      | -             | User identity information may be output in JSON format, for example: `userInfo:{ userCode:"001" }`, in the current example, `graySubKey` is `userCode`. |
-| `rules`         | array of object    | Required      | -             | User-defined different gray release rules, adapted to different gray release scenarios.                      |
-| `rewrite`       | object            | Required      | -             | Rewrite configuration, generally used for OSS/CDN frontend deployment rewrite configurations.                |
-| `baseDeployment`| object            | Optional      | -             | Configuration of the Base baseline rules.                                                                    |
-| `grayDeployments` | array of object   | Optional      | -             | Configuration of the effective rules for gray release, as well as the effective versions.                     |
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `grayKey` | string | Optional | - | Unique user identifier from Cookie/Header (e.g., userid). If empty, uses `rules[].grayTagKey` and `rules[].grayTagValue` to filter rules. |
+| `localStorageGrayKey` | string | Optional | - | When using JWT authentication, user ID comes from `localStorage`. Overrides `grayKey` if configured. |
+| `graySubKey` | string | Optional | - | Used when user info is in JSON format (e.g., `userInfo:{ userCode:"001" }`). In this example, `graySubKey` would be `userCode`. |
+| `storeMaxAge` | int | Optional | 31536000 | Max cookie storage duration in seconds (default: 1 year). |
+| `indexPaths` | string[] | Optional | - | Paths requiring mandatory processing (supports Glob patterns). Example: `/resource/**/manifest-main.json` in micro-frontend scenarios. |
+| `skippedPaths` | string[] | Optional | - | Excluded paths (supports Glob patterns). Example: `/api/**` XHR requests in rewrite scenarios. |
+| `skippedByHeaders` | map<string, string> | Optional | - | Filter requests via headers. `skippedPaths` has higher priority. HTML page requests are unaffected. |
+| `rules` | object[] | Required | - | User-defined grayscale rules for different scenarios. |
+| `rewrite` | object | Required | - | Rewrite configuration for OSS/CDN deployments. |
+| `baseDeployment` | object | Optional | - | Baseline configuration. |
+| `grayDeployments` | object[] | Optional | - | Gray deployment rules and versions. |
+| `backendGrayTag` | string | Optional | `x-mse-tag` | Backend grayscale tag. Cookies will carry `${backendGrayTag}:${grayDeployments[].backendVersion}` if configured. |
+| `uniqueGrayTag` | string | Optional | `x-higress-uid` | UUID stored in cookies for percentage-based grayscale session stickiness and backend tracking. |
+| `injection` | object | Optional | - | Inject global info into HTML (e.g., `<script>window.global = {...}</script>`). |
 
-`rules` field configuration description:
-| Name             | Data Type         | Requirements  | Default Value | Description                                                                                |
-|------------------|-------------------|---------------|---------------|--------------------------------------------------------------------------------------------|
-| `name`           | string            | Required      | -             | Unique identifier for the rule name, associated with `deploy.gray[].name` for effectiveness. |
-| `grayKeyValue`   | array of string   | Optional      | -             | Whitelist of user IDs.                                                                    |
-| `grayTagKey`     | string            | Optional      | -             | Label key for user classification tagging, derived from Cookie.                               |
-| `grayTagValue`   | array of string   | Optional      | -             | Label value for user classification tagging, derived from Cookie.                             |
+### `rules` Field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `name` | string | Required | - | Unique rule name linked to `grayDeployments[].name`. |
+| `grayKeyValue` | string[] | Optional | - | User ID whitelist. |
+| `grayTagKey` | string | Optional | - | User tag key from cookies. |
+| `grayTagValue` | string[] | Optional | - | User tag values from cookies. |
 
-`rewrite` field configuration description:
-> `indexRouting` homepage rewrite and `fileRouting` file rewrite essentially use prefix matching, for example, `/app1`: `/mfe/app1/{version}/index.html` represents requests with the prefix /app1 routed to `/mfe/app1/{version}/index.html` page, where `{version}` represents the version number, which will be dynamically replaced by `baseDeployment.version` or `grayDeployments[].version` during execution.  
-> `{version}` will be replaced dynamically during execution by the frontend version from `baseDeployment.version` or `grayDeployments[].version`.
+### `rewrite` Field
+> Both `indexRouting` and `fileRouting` use prefix matching. The `{version}` placeholder will be dynamically replaced by `baseDeployment.version` or `grayDeployments[].version`.
 
-| Name             | Data Type         | Requirements  | Default Value | Description                           |
-|------------------|-------------------|---------------|---------------|---------------------------------------|
-| `host`           | string            | Optional      | -             | Host address, if OSS set to the VPC internal access address. |
-| `notFoundUri`    | string            | Optional      | -             | 404 page configuration.               |
-| `indexRouting`   | map of string to string | Optional  | -             | Defines the homepage rewrite routing rules. Each key represents the homepage routing path, and the value points to the redirect target file. For example, the key `/app1` corresponds to the value `/mfe/app1/{version}/index.html`. If the effective version is `0.0.1`, the access path is `/app1`, it redirects to `/mfe/app1/0.0.1/index.html`. |
-| `fileRouting`    | map of string to string | Optional  | -             | Defines resource file rewrite routing rules. Each key represents the resource access path, and the value points to the redirect target file. For example, the key `/app1/` corresponds to the value `/mfe/app1/{version}`. If the effective version is `0.0.1`, the access path is `/app1/js/a.js`, it redirects to `/mfe/app1/0.0.1/js/a.js`. |
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `host` | string | Optional | - | Host address (use VPC endpoint for OSS). |
+| `indexRouting` | map<string, string> | Optional | - | Homepage rewrite rules. Key: route path, Value: target file. Example: `/app1` → `/mfe/app1/{version}/index.html`. |
+| `fileRouting` | map<string, string> | Optional | - | Resource rewrite rules. Key: resource path, Value: target path. Example: `/app1/` → `/mfe/app1/{version}`. |
 
-`baseDeployment` field configuration description:
-| Name             | Data Type         | Requirements  | Default Value | Description                                                                                |
-|------------------|-------------------|---------------|---------------|-------------------------------------------------------------------------------------------|
-| `version`        | string            | Required      | -             | The version number of the Base version, as a fallback version.                           |
+### `baseDeployment` Field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `version` | string | Required | - | Baseline version as fallback. |
+| `backendVersion` | string | Required | - | Backend grayscale version written to cookies via `${backendGrayTag}`. |
+| `versionPredicates` | string | Required | - | Supports multi-version mapping for micro-frontend scenarios. |
 
-`grayDeployments` field configuration description:
-| Name             | Data Type         | Requirements  | Default Value | Description                                                                                  |
-|------------------|-------------------|---------------|---------------|----------------------------------------------------------------------------------------------|
-| `version`        | string            | Required      | -             | Version number of the Gray version, if the gray rules are hit, this version will be used. If it is a non-CDN deployment, add `x-higress-tag` to the header. |
-| `backendVersion` | string            | Required      | -             | Gray version for the backend, which will add `x-mse-tag` to the header of `XHR/Fetch` requests. |
-| `name`           | string            | Required      | -             | Rule name associated with `rules[].name`.                                                  |
-| `enabled`        | boolean           | Required      | -             | Whether to activate the current gray release rule.                                          |
+### `grayDeployments` Field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `version` | string | Required | - | Gray version used when rules match. Adds `x-higress-tag` header for non-CDN deployments. |
+| `versionPredicates` | string | Required | - | Multi-version support for micro-frontends. |
+| `backendVersion` | string | Required | - | Backend grayscale version for cookies. |
+| `name` | string | Required | - | Linked to `rules[].name`. |
+| `enabled` | boolean | Required | - | Enable/disable rule. |
+| `weight` | int | Optional | - | Traffic percentage (e.g., 50). |
 
-## Configuration Example
-### Basic Configuration
-```yml
-grayKey: userid
-rules:
-- name: inner-user
-  grayKeyValue:
-  - '00000001'
-  - '00000005'
-- name: beta-user
-  grayKeyValue:
-  - '00000002'
-  - '00000003'
-  grayTagKey: level
-  grayTagValue:
-  - level3
-  - level5
-baseDeployment:
-  version: base
-grayDeployments:
-  - name: beta-user
-    version: gray
-    enabled: true
-```
+> **Percentage-based Grayscale Notes**:
+> 1. Percentage rules override user-based rules when both exist.
+> 2. Uses UUID fingerprint hashed via SHA-256 for traffic distribution.
 
-The unique identifier of the user in the cookie is `userid`, and the current gray release rule has configured the `beta-user` rule.  
-When the following conditions are met, the version `version: gray` will be used:
-- `userid` in the cookie equals `00000002` or `00000003`
-- Users whose `level` in the cookie equals `level3` or `level5`  
-Otherwise, use version `version: base`.
+### `injection` Field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `globalConfig` | object | Optional | - | Global variables injected into HTML. |
+| `head` | string[] | Optional | - | Inject elements into `<head>`. |
+| `body` | object | Optional | - | Inject elements into `<body>`. |
 
-### User Information Exists in JSON
-```yml
-grayKey: appInfo
-graySubKey: userId
-rules:
-- name: inner-user
-  grayKeyValue:
-  - '00000001'
-  - '00000005'
-- name: beta-user
-  grayKeyValue:
-  - '00000002'
-  - '00000003'
-  grayTagKey: level
-  grayTagValue:
-  - level3
-  - level5
-baseDeployment:
-  version: base
-grayDeployments:
-  - name: beta-user
-    version: gray
-    enabled: true
-```
+#### `globalConfig` Sub-field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `key` | string | Optional | `HIGRESS_CONSOLE_CONFIG` | Window global variable key. |
+| `featureKey` | string | Optional | `FEATURE_STATUS` | Rule hit status (e.g., `{"beta-user":true}`). |
+| `value` | string | Optional | - | Custom global value. |
+| `enabled` | boolean | Optional | `false` | Enable global injection. |
 
-The cookie contains JSON data for `appInfo`, which includes the field `userId` as the current unique identifier.  
-The current gray release rule has configured the `beta-user` rule.  
-When the following conditions are met, the version `version: gray` will be used:
-- `userid` in the cookie equals `00000002` or `00000003`
-- Users whose `level` in the cookie equals `level3` or `level5`  
-Otherwise, use version `version: base`.
+#### `body` Sub-field
+| Name | Data Type | Required | Default | Description |
+|------|-----------|----------|---------|-------------|
+| `first` | string[] | Optional | - | Inject at body start. |
+| `after` | string[] | Optional | - | Inject at body end. |
 
-### Rewrite Configuration
-> Generally used in CDN deployment scenarios.
+## Configuration Examples
+### Basic Configuration (User-based)
 ```yml
 grayKey: userid
 rules:
@@ -132,31 +105,9 @@ rules:
   grayTagValue:
   - level3
   - level5
-rewrite:
-  host: frontend-gray.oss-cn-shanghai-internal.aliyuncs.com
-  notFoundUri: /mfe/app1/dev/404.html
-  indexRouting:
-    /app1: '/mfe/app1/{version}/index.html'
-    /: '/mfe/app1/{version}/index.html',
-  fileRouting:
-    /: '/mfe/app1/{version}'
-    /app1/: '/mfe/app1/{version}'
 baseDeployment:
   version: base
 grayDeployments:
   - name: beta-user
     version: gray
     enabled: true
-```
-
-`{version}` will be dynamically replaced with the actual version during execution.
-
-#### indexRouting: Homepage Route Configuration
-Accessing `/app1`, `/app123`, `/app1/index.html`, `/app1/xxx`, `/xxxx` will route to '/mfe/app1/{version}/index.html'.
-
-#### fileRouting: File Route Configuration
-The following file mappings are effective:
-- `/js/a.js` => `/mfe/app1/v1.0.0/js/a.js`
-- `/js/template/a.js` => `/mfe/app1/v1.0.0/js/template/a.js`
-- `/app1/js/a.js` => `/mfe/app1/v1.0.0/js/a.js`
-- `/app1/js/template/a.js` => `/mfe/app1/v1.0.0/js/template/a.js`
diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go
index 43d7225c4c..ef0f4ab521 100644
--- a/plugins/wasm-go/extensions/frontend-gray/config/config.go
+++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go
@@ -1,23 +1,23 @@
 package config
 
 import (
+	"errors"
+	"path/filepath"
+	"regexp"
+	"strconv"
 	"strings"
 
 	"github.com/tidwall/gjson"
 )
 
 const (
-	XHigressTag     = "x-higress-tag"
-	XUniqueClientId = "x-unique-client"
-	XPreHigressTag  = "x-pre-higress-tag"
-	IsPageRequest   = "is-page-request"
-	IsNotFound      = "is-not-found"
-	EnabledGray     = "enabled-gray"
-	SecFetchMode    = "sec-fetch-mode"
+	XHigressTag       = "x-higress-tag"
+	PreHigressVersion = "pre-higress-version"
+	IsHtmlRequest     = "is-html-request"
+	IsIndexRequest    = "is-index-request"
+	EnabledGray       = "enabled-gray"
 )
 
-type LogInfo func(format string, args ...interface{})
-
 type GrayRule struct {
 	Name         string
 	GrayKeyValue []string
@@ -35,15 +35,22 @@ type Deployment struct {
 }
 
 type Rewrite struct {
-	Host     string
-	NotFound string
-	Index    map[string]string
-	File     map[string]string
+	Host  string
+	Index map[string]string
+	File  map[string]string
 }
 
 type Injection struct {
-	Head []string
-	Body *BodyInjection
+	GlobalConfig *GlobalConfig
+	Head         []string
+	Body         *BodyInjection
+}
+
+type GlobalConfig struct {
+	Key        string
+	FeatureKey string
+	Value      string
+	Enabled    bool
 }
 
 type BodyInjection struct {
@@ -52,8 +59,7 @@ type BodyInjection struct {
 }
 
 type GrayConfig struct {
-	UserStickyMaxAge    string
-	TotalGrayWeight     int
+	StoreMaxAge         int
 	GrayKey             string
 	LocalStorageGrayKey string
 	GraySubKey          string
@@ -63,10 +69,26 @@ type GrayConfig struct {
 	BaseDeployment      *Deployment
 	GrayDeployments     []*Deployment
 	BackendGrayTag      string
+	UniqueGrayTag       string
 	Injection           *Injection
-	SkippedPathPrefixes []string
-	IncludePathPrefixes []string
+	SkippedPaths        []string
 	SkippedByHeaders    map[string]string
+	IndexPaths          []string
+	GrayWeight          int
+}
+
+func isValidName(s string) bool {
+	// 定义一个正则表达式，匹配字母、数字和下划线
+	re := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
+	return re.MatchString(s)
+}
+
+func GetWithDefault(json gjson.Result, path, defaultValue string) string {
+	res := json.Get(path)
+	if res.Exists() {
+		return res.String()
+	}
+	return defaultValue
 }
 
 func convertToStringList(results []gjson.Result) []string {
@@ -77,6 +99,22 @@ func convertToStringList(results []gjson.Result) []string {
 	return interfaces
 }
 
+func compatibleConvertToStringList(results []gjson.Result, compatibleResults []gjson.Result) []string {
+	// 优先使用兼容模式的数据
+	if len(compatibleResults) == 0 {
+		interfaces := make([]string, len(results)) // 预分配切片容量
+		for i, result := range results {
+			interfaces[i] = result.String() // 使用 String() 方法直接获取字符串
+		}
+		return interfaces
+	}
+	compatibleInterfaces := make([]string, len(compatibleResults)) // 预分配切片容量
+	for i, result := range compatibleResults {
+		compatibleInterfaces[i] = filepath.Join(result.String(), "**")
+	}
+	return compatibleInterfaces
+}
+
 func convertToStringMap(result gjson.Result) map[string]string {
 	m := make(map[string]string)
 	result.ForEach(func(key, value gjson.Result) bool {
@@ -86,7 +124,7 @@ func convertToStringMap(result gjson.Result) map[string]string {
 	return m
 }
 
-func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
+func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) error {
 	// 解析 GrayKey
 	grayConfig.LocalStorageGrayKey = json.Get("localStorageGrayKey").String()
 	grayConfig.GrayKey = json.Get("grayKey").String()
@@ -94,22 +132,18 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
 		grayConfig.GrayKey = grayConfig.LocalStorageGrayKey
 	}
 	grayConfig.GraySubKey = json.Get("graySubKey").String()
-	grayConfig.BackendGrayTag = json.Get("backendGrayTag").String()
-	grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String()
-	grayConfig.Html = json.Get("html").String()
-	grayConfig.SkippedPathPrefixes = convertToStringList(json.Get("skippedPathPrefixes").Array())
-	grayConfig.SkippedByHeaders = convertToStringMap(json.Get("skippedByHeaders"))
-	grayConfig.IncludePathPrefixes = convertToStringList(json.Get("includePathPrefixes").Array())
-
-	if grayConfig.UserStickyMaxAge == "" {
-		// 默认值2天
-		grayConfig.UserStickyMaxAge = "172800"
-	}
-
-	if grayConfig.BackendGrayTag == "" {
-		grayConfig.BackendGrayTag = "x-mse-tag"
+	grayConfig.BackendGrayTag = GetWithDefault(json, "backendGrayTag", "x-mse-tag")
+	grayConfig.UniqueGrayTag = GetWithDefault(json, "uniqueGrayTag", "x-higress-uid")
+	grayConfig.StoreMaxAge = 60 * 60 * 24 * 365 // 默认一年
+	storeMaxAge, err := strconv.Atoi(GetWithDefault(json, "StoreMaxAge", strconv.Itoa(grayConfig.StoreMaxAge)))
+	if err != nil {
+		grayConfig.StoreMaxAge = storeMaxAge
 	}
 
+	grayConfig.Html = json.Get("html").String()
+	grayConfig.SkippedPaths = compatibleConvertToStringList(json.Get("skippedPaths").Array(), json.Get("skippedPathPrefixes").Array())
+	grayConfig.IndexPaths = compatibleConvertToStringList(json.Get("indexPaths").Array(), json.Get("includePathPrefixes").Array())
+	grayConfig.SkippedByHeaders = convertToStringMap(json.Get("skippedByHeaders"))
 	// 解析 Rules
 	rules := json.Get("rules").Array()
 	for _, rule := range rules {
@@ -122,10 +156,9 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
 		grayConfig.Rules = append(grayConfig.Rules, &grayRule)
 	}
 	grayConfig.Rewrite = &Rewrite{
-		Host:     json.Get("rewrite.host").String(),
-		NotFound: json.Get("rewrite.notFoundUri").String(),
-		Index:    convertToStringMap(json.Get("rewrite.indexRouting")),
-		File:     convertToStringMap(json.Get("rewrite.fileRouting")),
+		Host:  json.Get("rewrite.host").String(),
+		Index: convertToStringMap(json.Get("rewrite.indexRouting")),
+		File:  convertToStringMap(json.Get("rewrite.fileRouting")),
 	}
 
 	// 解析 deployment
@@ -134,6 +167,7 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
 
 	grayConfig.BaseDeployment = &Deployment{
 		Name:              baseDeployment.Get("name").String(),
+		BackendVersion:    baseDeployment.Get("backendVersion").String(),
 		Version:           strings.Trim(baseDeployment.Get("version").String(), " "),
 		VersionPredicates: convertToStringMap(baseDeployment.Get("versionPredicates")),
 	}
@@ -141,16 +175,28 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
 		if !item.Get("enabled").Bool() {
 			continue
 		}
-		grayWeight := int(item.Get("weight").Int())
+		weight := int(item.Get("weight").Int())
 		grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{
 			Name:              item.Get("name").String(),
 			Enabled:           item.Get("enabled").Bool(),
 			Version:           strings.Trim(item.Get("version").String(), " "),
 			BackendVersion:    item.Get("backendVersion").String(),
-			Weight:            grayWeight,
+			Weight:            weight,
 			VersionPredicates: convertToStringMap(item.Get("versionPredicates")),
 		})
-		grayConfig.TotalGrayWeight += grayWeight
+		if weight > 0 {
+			grayConfig.GrayWeight = weight
+			break
+		}
+	}
+
+	injectGlobalFeatureKey := GetWithDefault(json, "injection.globalConfig.featureKey", "FEATURE_STATUS")
+	injectGlobalKey := GetWithDefault(json, "injection.globalConfig.key", "HIGRESS_CONSOLE_CONFIG")
+	if !isValidName(injectGlobalFeatureKey) {
+		return errors.New("injection.globalConfig.featureKey is invalid")
+	}
+	if !isValidName(injectGlobalKey) {
+		return errors.New("injection.globalConfig.featureKey is invalid")
 	}
 
 	grayConfig.Injection = &Injection{
@@ -159,5 +205,12 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
 			First: convertToStringList(json.Get("injection.body.first").Array()),
 			Last:  convertToStringList(json.Get("injection.body.last").Array()),
 		},
+		GlobalConfig: &GlobalConfig{
+			FeatureKey: injectGlobalFeatureKey,
+			Key:        injectGlobalKey,
+			Value:      json.Get("injection.globalConfig.value").String(),
+			Enabled:    json.Get("injection.globalConfig.enabled").Bool(),
+		},
 	}
+	return nil
 }
diff --git a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml
index bc584d5ccd..b576c32498 100644
--- a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml
+++ b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml
@@ -48,8 +48,8 @@ static_resources:
                             value: |
                               {
                                 "grayKey": "userId",
-                                "backendGrayTag": "x-mse-tag",
-                                "userStickyMaxAge": 172800,
+                                "backendGrayTag": "env",
+                                "uniqueGrayTag": "uuid",
                                 "rules": [
                                   {
                                     "name": "inner-user",
@@ -72,30 +72,44 @@ static_resources:
                                   }
                                 ],
                                 "rewrite": {
-                                  "host": "frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com",
+                                  "host": "apig-oss-integration.oss-cn-hangzhou.aliyuncs.com",
                                   "indexRouting": {
-                                    "/app1": "/mfe/app1/{version}/index.html",
-                                    "/": "/mfe/app1/{version}/index.html"
+                                    "/": "/mfe/{version}/index.html"
                                   },
                                   "fileRouting": {
-                                    "/": "/mfe/app1/{version}",
-                                    "/app1": "/mfe/app1/{version}"
+                                    "/": "/mfe/{version}",
+                                    "/mfe": "/mfe/{version}"
                                   }
                                 },
-                                "skippedPathPrefixes": [
-                                  "/api/"
+                                "skippedPaths": [
+                                  "/api/**",
+                                  "/v2/**"
+                                ],
+                                "indexPaths": [
+                                  "/mfe/**/mf-manifest-main.json"
                                 ],
                                 "baseDeployment": {
-                                  "version": "dev"
+                                  "version": "v1"
                                 },
                                 "grayDeployments": [
                                   {
+                                    "weight": 90,
                                     "name": "beta-user",
-                                    "version": "0.0.1",
-                                    "enabled": true
+                                    "version": "v2",
+                                    "enabled": true,
+                                    "backendVersion":"gray",
+                                    "versionPredicates": {
+                                      "/mfe": "v1"
+                                    }
                                   }
                                 ],
                                 "injection": {
+                                  "globalConfig": {
+                                    "key": "HIGRESS_CONSOLE_CONFIG",
+                                    "featureKey": "FEATURE_STATUS",
+                                    "value": "{CONSOLE_GLOBAL: {'gray':'2.0.15','main':'2.0.15'}}",
+                                    "enabled": true
+                                  },
                                   "head": [
                                     "<script>console.log('Header')</script>"
                                   ],
@@ -127,5 +141,5 @@ static_resources:
               - endpoint:
                   address:
                     socket_address:
-                      address: frontend-gray-cn-shanghai.oss-cn-shanghai.aliyuncs.com
+                      address: apig-oss-integration.oss-cn-hangzhou.aliyuncs.com
                       port_value: 80
diff --git a/plugins/wasm-go/extensions/frontend-gray/go.mod b/plugins/wasm-go/extensions/frontend-gray/go.mod
index 4900b839d2..6bd16198db 100644
--- a/plugins/wasm-go/extensions/frontend-gray/go.mod
+++ b/plugins/wasm-go/extensions/frontend-gray/go.mod
@@ -6,6 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
 
 require (
 	github.com/alibaba/higress/plugins/wasm-go v1.4.3-0.20240727022514-bccfbde62188
+	github.com/bmatcuk/doublestar/v4 v4.6.1
 	github.com/higress-group/proxy-wasm-go-sdk v1.0.0
 	github.com/stretchr/testify v1.9.0
 	github.com/tidwall/gjson v1.17.3
diff --git a/plugins/wasm-go/extensions/frontend-gray/go.sum b/plugins/wasm-go/extensions/frontend-gray/go.sum
index 8143975d89..974ba746ea 100644
--- a/plugins/wasm-go/extensions/frontend-gray/go.sum
+++ b/plugins/wasm-go/extensions/frontend-gray/go.sum
@@ -1,6 +1,8 @@
-github.com/davecgh/go-spew v1.1.1+incompatible/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
+github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1+incompatible/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
@@ -11,9 +13,9 @@ github.com/higress-group/proxy-wasm-go-sdk v1.0.0 h1:BZRNf4R7jr9hwRivg/E29nkVaKE
 github.com/higress-group/proxy-wasm-go-sdk v1.0.0/go.mod h1:iiSyFbo+rAtbtGt/bsefv8GU57h9CCLYGJA74/tF5/0=
 github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
 github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
-github.com/pmezard/go-difflib v1.0.0+incompatible/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.0+incompatible/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go
index c238b26f4b..f561166d5d 100644
--- a/plugins/wasm-go/extensions/frontend-gray/main.go
+++ b/plugins/wasm-go/extensions/frontend-gray/main.go
@@ -2,13 +2,11 @@ package main
 
 import (
 	"fmt"
-	"net/http"
-	"net/url"
-	"path"
 	"strings"
 
 	"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config"
 	"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/util"
+	"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
 
 	"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
 	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
@@ -19,44 +17,42 @@ import (
 func main() {
 	wrapper.SetCtx(
 		"frontend-gray",
-		wrapper.ParseConfigBy(parseConfig),
-		wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
-		wrapper.ProcessResponseHeadersBy(onHttpResponseHeader),
-		wrapper.ProcessResponseBodyBy(onHttpResponseBody),
+		wrapper.ParseConfig(parseConfig),
+		wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
+		wrapper.ProcessResponseHeaders(onHttpResponseHeader),
+		wrapper.ProcessResponseBody(onHttpResponseBody),
 	)
 }
 
-func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error {
+func parseConfig(json gjson.Result, grayConfig *config.GrayConfig) error {
 	// 解析json 为GrayConfig
-	config.JsonToGrayConfig(json, grayConfig)
-	log.Infof("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments"))
+	if err := config.JsonToGrayConfig(json, grayConfig); err != nil {
+		log.Errorf("failed to parse config: %v", err)
+		return err
+	}
+
 	return nil
 }
 
-func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action {
-	requestPath, _ := proxywasm.GetHttpRequestHeader(":path")
-	requestPath = path.Clean(requestPath)
-	parsedURL, err := url.Parse(requestPath)
-	if err == nil {
-		requestPath = parsedURL.Path
-	} else {
-		log.Errorf("parse request path %s failed: %v", requestPath, err)
-	}
-	enabledGray := util.IsGrayEnabled(grayConfig, requestPath)
+func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig) types.Action {
+	requestPath := util.GetRequestPath()
+	enabledGray := util.IsGrayEnabled(requestPath, &grayConfig)
 	ctx.SetContext(config.EnabledGray, enabledGray)
-	secFetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode")
-	ctx.SetContext(config.SecFetchMode, secFetchMode)
+	route, _ := util.GetRouteName()
 
 	if !enabledGray {
-		log.Infof("gray not enabled")
+		log.Infof("route: %s, gray not enabled, requestPath: %v", route, requestPath)
 		ctx.DontReadRequestBody()
 		return types.ActionContinue
 	}
 
-	cookies, _ := proxywasm.GetHttpRequestHeader("cookie")
-	isPageRequest := util.IsPageRequest(requestPath)
+	cookie, _ := proxywasm.GetHttpRequestHeader("cookie")
+	isHtmlRequest := util.CheckIsHtmlRequest(requestPath)
+	ctx.SetContext(config.IsHtmlRequest, isHtmlRequest)
+	isIndexRequest := util.IsIndexRequest(requestPath, grayConfig.IndexPaths)
+	ctx.SetContext(config.IsIndexRequest, isIndexRequest)
 	hasRewrite := len(grayConfig.Rewrite.File) > 0 || len(grayConfig.Rewrite.Index) > 0
-	grayKeyValueByCookie := util.ExtractCookieValueByKey(cookies, grayConfig.GrayKey)
+	grayKeyValueByCookie := util.GetCookieValue(cookie, grayConfig.GrayKey)
 	grayKeyValueByHeader, _ := proxywasm.GetHttpRequestHeader(grayConfig.GrayKey)
 	// 优先从cookie中获取，否则从header中获取
 	grayKeyValue := util.GetGrayKey(grayKeyValueByCookie, grayKeyValueByHeader, grayConfig.GraySubKey)
@@ -65,93 +61,92 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
 		// 禁止重新路由，要在更改Header之前操作，否则会失效
 		ctx.DisableReroute()
 	}
+	frontendVersion := util.GetCookieValue(cookie, config.XHigressTag)
+
+	if grayConfig.GrayWeight > 0 {
+		ctx.SetContext(grayConfig.UniqueGrayTag, util.GetGrayWeightUniqueId(cookie, grayConfig.UniqueGrayTag))
+	}
 
 	// 删除Accept-Encoding，避免压缩， 如果是压缩的内容，后续插件就没法处理了
 	_ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
 	_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
 	deployment := &config.Deployment{}
 
-	preVersion, preUniqueClientId := util.GetXPreHigressVersion(cookies)
-	// 客户端唯一ID，用于在按照比率灰度时候 客户访问黏贴
-	uniqueClientId := grayKeyValue
-	if uniqueClientId == "" {
-		xForwardedFor, _ := proxywasm.GetHttpRequestHeader("X-Forwarded-For")
-		uniqueClientId = util.GetRealIpFromXff(xForwardedFor)
+	globalConfig := grayConfig.Injection.GlobalConfig
+	if globalConfig.Enabled {
+		conditionRule := util.GetConditionRules(grayConfig.Rules, grayKeyValue, cookie)
+		trimmedValue := strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(globalConfig.Value), "{"), "}")
+		ctx.SetContext(globalConfig.Key, fmt.Sprintf("<script>var %s = {\n%s:%s,\n %s \n}\n</script>", globalConfig.Key, globalConfig.FeatureKey, conditionRule, trimmedValue))
 	}
 
-	// 如果没有配置比例，则进行灰度规则匹配
-	if util.IsSupportMultiVersion(grayConfig) {
-		deployment = util.FilterMultiVersionGrayRule(&grayConfig, grayKeyValue, requestPath)
-		log.Infof("multi version %v", deployment)
+	if isHtmlRequest {
+		// index首页请求每次都会进度灰度规则判断
+		deployment = util.FilterGrayRule(&grayConfig, grayKeyValue, cookie)
+		log.Infof("route: %s, index html request: %v, backend: %v, xPreHigressVersion: %s", route, requestPath, deployment.BackendVersion, frontendVersion)
+		ctx.SetContext(config.PreHigressVersion, deployment.Version)
+		ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion)
 	} else {
-		if isPageRequest {
-			if grayConfig.TotalGrayWeight > 0 {
-				log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight)
-				deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId)
+		if util.IsSupportMultiVersion(grayConfig) {
+			deployment = util.FilterMultiVersionGrayRule(&grayConfig, grayKeyValue, cookie, requestPath)
+			log.Infof("route: %s, multi version %v", route, deployment)
+		} else {
+			grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue, cookie)
+			if isIndexRequest {
+				deployment = grayDeployment
 			} else {
-				deployment = util.FilterGrayRule(&grayConfig, grayKeyValue)
+				deployment = util.GetVersion(grayConfig, grayDeployment, frontendVersion)
 			}
-			log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId)
-		} else {
-			grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue)
-			deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest)
 		}
-		ctx.SetContext(config.XPreHigressTag, deployment.Version)
-		ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion)
 	}
-
 	proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version)
 
-	ctx.SetContext(config.IsPageRequest, isPageRequest)
-	ctx.SetContext(config.XUniqueClientId, uniqueClientId)
-
 	rewrite := grayConfig.Rewrite
 	if rewrite.Host != "" {
 		err := proxywasm.ReplaceHttpRequestHeader(":authority", rewrite.Host)
 		if err != nil {
-			log.Errorf("host rewrite failed: %v", err)
+			log.Errorf("route: %s, host rewrite failed: %v", route, err)
 		}
 	}
 
 	if hasRewrite {
 		rewritePath := requestPath
-		if isPageRequest {
+		if isHtmlRequest {
 			rewritePath = util.IndexRewrite(requestPath, deployment.Version, grayConfig.Rewrite.Index)
 		} else {
 			rewritePath = util.PrefixFileRewrite(requestPath, deployment.Version, grayConfig.Rewrite.File)
 		}
 		if requestPath != rewritePath {
-			log.Infof("rewrite path:%s, rewritePath:%s, Version:%v", requestPath, rewritePath, deployment.Version)
+			log.Infof("route: %s, rewrite path:%s, rewritePath:%s, Version:%v", route, requestPath, rewritePath, deployment.Version)
 			proxywasm.ReplaceHttpRequestHeader(":path", rewritePath)
 		}
 	}
-	log.Infof("request path:%s, has rewrited:%v, rewrite config:%+v", requestPath, hasRewrite, rewrite)
 	return types.ActionContinue
 }
 
-func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action {
+func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig) types.Action {
 	enabledGray, _ := ctx.GetContext(config.EnabledGray).(bool)
 	if !enabledGray {
 		ctx.DontReadResponseBody()
 		return types.ActionContinue
 	}
-	secFetchMode, isSecFetchModeOk := ctx.GetContext(config.SecFetchMode).(string)
-	if isSecFetchModeOk && secFetchMode == "cors" {
+	isIndexRequest, indexOk := ctx.GetContext(config.IsIndexRequest).(bool)
+	if indexOk && isIndexRequest {
+		// 首页请求强制不缓存
 		proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
+		ctx.DontReadResponseBody()
+		return types.ActionContinue
 	}
-	isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool)
-	if !ok {
-		isPageRequest = false // 默认值
-	}
+
+	isHtmlRequest, htmlOk := ctx.GetContext(config.IsHtmlRequest).(bool)
 	// response 不处理非首页的请求
-	if !isPageRequest {
+	if !htmlOk || !isHtmlRequest {
 		ctx.DontReadResponseBody()
 		return types.ActionContinue
 	} else {
 		// 不会进去Streaming 的Body处理
 		ctx.BufferResponseBody()
 	}
-
+	// 处理HTML的首页
 	status, err := proxywasm.GetHttpResponseHeader(":status")
 	if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
 		// 删除Content-Disposition，避免自动下载文件
@@ -163,117 +158,81 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
 
 	// 处理code为 200的情况
 	if err != nil || status != "200" {
-		if status == "404" {
-			if grayConfig.Rewrite.NotFound != "" && isPageRequest {
-				ctx.SetContext(config.IsNotFound, true)
-				responseHeaders, _ := proxywasm.GetHttpResponseHeaders()
-				headersMap := util.ConvertHeaders(responseHeaders)
-				if _, ok := headersMap[":status"]; !ok {
-					headersMap[":status"] = []string{"200"} // 如果没有初始化，设定默认值
-				} else {
-					headersMap[":status"][0] = "200" // 修改现有值
-				}
-				if _, ok := headersMap["content-type"]; !ok {
-					headersMap["content-type"] = []string{"text/html"} // 如果没有初始化，设定默认值
-				} else {
-					headersMap["content-type"][0] = "text/html" // 修改现有值
-				}
-				// 删除 content-length 键
-				delete(headersMap, "content-length")
-				proxywasm.ReplaceHttpResponseHeaders(util.ReconvertHeaders(headersMap))
-				ctx.BufferResponseBody()
-				return types.ActionContinue
-			} else {
-				// 直接返回400
-				ctx.DontReadResponseBody()
-			}
+		// 如果找不到HTML，但配置了HTML页面
+		if status == "404" && grayConfig.Html != "" {
+			responseHeaders, _ := proxywasm.GetHttpResponseHeaders()
+			headersMap := util.ConvertHeaders(responseHeaders)
+			delete(headersMap, "content-length")
+			headersMap[":status"][0] = "200"
+			headersMap["content-type"][0] = "text/html"
+			ctx.BufferResponseBody()
+			proxywasm.ReplaceHttpResponseHeaders(util.ReconvertHeaders(headersMap))
+		} else {
+			route, _ := util.GetRouteName()
+			log.Errorf("route: %s, request error code: %s, message: %v", route, status, err)
+			ctx.DontReadResponseBody()
+			return types.ActionContinue
 		}
-		log.Errorf("error status: %s, error message: %v", status, err)
-		return types.ActionContinue
-	}
-	cacheControl, _ := proxywasm.GetHttpResponseHeader("cache-control")
-	if !strings.Contains(cacheControl, "no-cache") {
-		proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
 	}
+	proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
 
-	frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string)
-	xUniqueClient, isUniqClientOk := ctx.GetContext(config.XUniqueClientId).(string)
-
-	// 设置前端的版本
-	if isFeVersionOk && isUniqClientOk && frontendVersion != "" {
-		proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge))
+	// 前端版本
+	frontendVersion, isFrontendVersionOk := ctx.GetContext(config.PreHigressVersion).(string)
+	if isFrontendVersionOk {
+		proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;", config.XHigressTag, frontendVersion, grayConfig.StoreMaxAge))
+	}
+	// 设置GrayWeight 唯一值
+	if grayConfig.GrayWeight > 0 {
+		uniqueId, isUniqueIdOk := ctx.GetContext(grayConfig.UniqueGrayTag).(string)
+		if isUniqueIdOk {
+			proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;", grayConfig.UniqueGrayTag, uniqueId, grayConfig.StoreMaxAge))
+		}
 	}
 	// 设置后端的版本
 	if util.IsBackendGrayEnabled(grayConfig) {
 		backendVersion, isBackVersionOk := ctx.GetContext(grayConfig.BackendGrayTag).(string)
-		if isBackVersionOk && backendVersion != "" {
-			proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge))
+		if isBackVersionOk {
+			if backendVersion == "" {
+				// 删除后端灰度版本
+				proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/;", grayConfig.BackendGrayTag, backendVersion))
+			} else {
+				proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.StoreMaxAge))
+			}
 		}
 	}
 	return types.ActionContinue
 }
 
-func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, body []byte, log wrapper.Log) types.Action {
+func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, body []byte) types.Action {
 	enabledGray, _ := ctx.GetContext(config.EnabledGray).(bool)
 	if !enabledGray {
 		return types.ActionContinue
 	}
-	isPageRequest, isPageRequestOk := ctx.GetContext(config.IsPageRequest).(bool)
-	frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string)
+	isHtmlRequest, isHtmlRequestOk := ctx.GetContext(config.IsHtmlRequest).(bool)
+	frontendVersion, isFeVersionOk := ctx.GetContext(config.PreHigressVersion).(string)
 	// 只处理首页相关请求
-	if !isFeVersionOk || !isPageRequestOk || !isPageRequest {
+	if !isFeVersionOk || !isHtmlRequestOk || !isHtmlRequest {
 		return types.ActionContinue
 	}
-
-	isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
-	if !ok {
-		isNotFound = false // 默认值
+	globalConfig := grayConfig.Injection.GlobalConfig
+	globalConfigValue, isGobalConfigOk := ctx.GetContext(globalConfig.Key).(string)
+	if !isGobalConfigOk {
+		globalConfigValue = ""
 	}
 
-	// 检查是否存在自定义 HTML， 如有则省略 rewrite.indexRouting 的内容
+	newHtml := string(body)
 	if grayConfig.Html != "" {
-		log.Debugf("Returning custom HTML from config.")
-		// 替换响应体为 config.Html 内容
-		if err := proxywasm.ReplaceHttpResponseBody([]byte(grayConfig.Html)); err != nil {
-			log.Errorf("Error replacing response body: %v", err)
-			return types.ActionContinue
-		}
-
-		newHtml := util.InjectContent(grayConfig.Html, grayConfig.Injection)
-		// 替换当前html加载的动态文件版本
-		newHtml = strings.ReplaceAll(newHtml, "{version}", frontendVersion)
-
-		// 最终替换响应体
-		if err := proxywasm.ReplaceHttpResponseBody([]byte(newHtml)); err != nil {
-			log.Errorf("Error replacing injected response body: %v", err)
-			return types.ActionContinue
-		}
-
-		return types.ActionContinue
+		newHtml = grayConfig.Html
 	}
+	newHtml = util.InjectContent(newHtml, grayConfig.Injection, globalConfigValue)
+	// 替换当前html加载的动态文件版本
+	newHtml = strings.ReplaceAll(newHtml, "{version}", frontendVersion)
+	newHtml = util.FixLocalStorageKey(newHtml, grayConfig.LocalStorageGrayKey)
 
-	// 针对404页面处理
-	if isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" {
-		client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: grayConfig.Rewrite.Host})
-
-		client.Get(strings.Replace(grayConfig.Rewrite.NotFound, "{version}", frontendVersion, -1), nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
-			proxywasm.ReplaceHttpResponseBody(responseBody)
-			proxywasm.ResumeHttpResponse()
-		}, 1500)
-		return types.ActionPause
-	}
-
-	// 处理响应体HTML
-	newBody := string(body)
-	newBody = util.InjectContent(newBody, grayConfig.Injection)
-	if grayConfig.LocalStorageGrayKey != "" {
-		localStr := strings.ReplaceAll(`<script>
-		!function(){var o,e,n="@@X_GRAY_KEY",t=document.cookie.split("; ").filter(function(o){return 0===o.indexOf(n+"=")});try{"undefined"!=typeof localStorage&&null!==localStorage&&(o=localStorage.getItem(n),e=0<t.length?decodeURIComponent(t[0].split("=")[1]):null,o)&&o.indexOf("=")<0&&e&&e!==o&&(document.cookie=n+"="+encodeURIComponent(o)+"; path=/;",window.location.reload())}catch(o){}}();
-		</script>
-		`, "@@X_GRAY_KEY", grayConfig.LocalStorageGrayKey)
-		newBody = strings.ReplaceAll(newBody, "<body>", "<body>\n"+localStr)
-	}
-	if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil {
+	// 最终替换响应体
+	if err := proxywasm.ReplaceHttpResponseBody([]byte(newHtml)); err != nil {
+		route, _ := util.GetRouteName()
+		log.Errorf("route: %s, Failed to replace response body: %v", route, err)
 		return types.ActionContinue
 	}
 	return types.ActionContinue
diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go
index 557bf3746c..031ec558b0 100644
--- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go
+++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go
@@ -1,15 +1,16 @@
 package util
 
 import (
-	"fmt"
-	"math/rand"
+	"encoding/json"
+	"hash/crc32"
 	"net/url"
 	"path"
 	"path/filepath"
 	"sort"
 	"strings"
-	"time"
 
+	"github.com/bmatcuk/doublestar/v4"
+	"github.com/google/uuid"
 	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
 
 	"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config"
@@ -17,43 +18,26 @@ import (
 	"github.com/tidwall/gjson"
 )
 
-func LogInfof(format string, args ...interface{}) {
-	format = fmt.Sprintf("[%s] %s", "frontend-gray", format)
-	proxywasm.LogInfof(format, args...)
-}
-
-func GetXPreHigressVersion(cookies string) (string, string) {
-	xPreHigressVersion := ExtractCookieValueByKey(cookies, config.XPreHigressTag)
-	preVersions := strings.Split(xPreHigressVersion, ",")
-	if len(preVersions) == 0 {
-		return "", ""
-	}
-	if len(preVersions) == 1 {
-		return preVersions[0], ""
+func GetRequestPath() string {
+	requestPath, _ := proxywasm.GetHttpRequestHeader(":path")
+	requestPath = path.Clean(requestPath)
+	parsedURL, err := url.Parse(requestPath)
+	if err == nil {
+		requestPath = parsedURL.Path
+	} else {
+		return ""
 	}
-
-	return strings.TrimSpace(preVersions[0]), strings.TrimSpace(preVersions[1])
+	return requestPath
 }
 
-// 从xff中获取真实的IP
-func GetRealIpFromXff(xff string) string {
-	if xff != "" {
-		// 通常客户端的真实 IP 是 XFF 头中的第一个 IP
-		ips := strings.Split(xff, ",")
-		if len(ips) > 0 {
-			return strings.TrimSpace(ips[0])
-		}
+func GetRouteName() (string, error) {
+	if raw, err := proxywasm.GetProperty([]string{"route_name"}); err != nil {
+		return "-", err
+	} else {
+		return string(raw), nil
 	}
-	return ""
 }
-
-func IsRequestSkippedByHeaders(grayConfig config.GrayConfig) bool {
-	secFetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode")
-	upgrade, _ := proxywasm.GetHttpRequestHeader("upgrade")
-	if len(grayConfig.SkippedByHeaders) == 0 {
-		// 默认不走插件逻辑的header
-		return secFetchMode == "cors" || upgrade == "websocket"
-	}
+func IsRequestSkippedByHeaders(grayConfig *config.GrayConfig) bool {
 	for headerKey, headerValue := range grayConfig.SkippedByHeaders {
 		requestHeader, _ := proxywasm.GetHttpRequestHeader(headerKey)
 		if requestHeader == headerValue {
@@ -63,34 +47,40 @@ func IsRequestSkippedByHeaders(grayConfig config.GrayConfig) bool {
 	return false
 }
 
-func IsGrayEnabled(grayConfig config.GrayConfig, requestPath string) bool {
-	for _, prefix := range grayConfig.IncludePathPrefixes {
-		if strings.HasPrefix(requestPath, prefix) {
+func IsIndexRequest(requestPath string, indexPaths []string) bool {
+	for _, prefix := range indexPaths {
+		matchResult, err := doublestar.Match(prefix, requestPath)
+		if err == nil && matchResult {
 			return true
 		}
 	}
+	return false
+}
 
-	// 当前路径中前缀为 SkippedPathPrefixes，则不走插件逻辑
-	for _, prefix := range grayConfig.SkippedPathPrefixes {
-		if strings.HasPrefix(requestPath, prefix) {
+func IsGrayEnabled(requestPath string, grayConfig *config.GrayConfig) bool {
+	if IsIndexRequest(requestPath, grayConfig.IndexPaths) {
+		return true
+	}
+	// 当前路径中前缀为 SkippedPaths，则不走插件逻辑
+	for _, prefix := range grayConfig.SkippedPaths {
+		matchResult, err := doublestar.Match(prefix, requestPath)
+		if err == nil && matchResult {
 			return false
 		}
 	}
 
 	//  如果是首页，进入插件逻辑
-	if IsPageRequest(requestPath) {
+	if CheckIsHtmlRequest(requestPath) {
 		return true
 	}
-	// 检查header标识，判断是否需要跳过
-	if IsRequestSkippedByHeaders(grayConfig) {
-		return false
-	}
-
 	// 检查是否存在重写主机
 	if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
 		return true
 	}
-
+	// 检查header标识，判断是否需要跳过
+	if IsRequestSkippedByHeaders(grayConfig) {
+		return false
+	}
 	// 检查是否存在灰度版本配置
 	return len(grayConfig.GrayDeployments) > 0
 }
@@ -105,8 +95,8 @@ func IsBackendGrayEnabled(grayConfig config.GrayConfig) bool {
 	return false
 }
 
-// ExtractCookieValueByKey 根据 cookie 和 key 获取 cookie 值
-func ExtractCookieValueByKey(cookie string, key string) string {
+// GetCookieValue 根据 cookie 和 key 获取 cookie 值
+func GetCookieValue(cookie string, key string) string {
 	if cookie == "" {
 		return ""
 	}
@@ -170,7 +160,7 @@ var indexSuffixes = []string{
 	".html", ".htm", ".jsp", ".php", ".asp", ".aspx", ".erb", ".ejs", ".twig",
 }
 
-func IsPageRequest(requestPath string) bool {
+func CheckIsHtmlRequest(requestPath string) bool {
 	if requestPath == "/" || requestPath == "" {
 		return true
 	}
@@ -227,10 +217,7 @@ func PrefixFileRewrite(path, version string, matchRules map[string]string) strin
 	return path
 }
 
-func GetVersion(grayConfig config.GrayConfig, deployment *config.Deployment, xPreHigressVersion string, isPageRequest bool) *config.Deployment {
-	if isPageRequest {
-		return deployment
-	}
+func GetVersion(grayConfig config.GrayConfig, deployment *config.Deployment, xPreHigressVersion string) *config.Deployment {
 	// cookie 中为空，返回当前版本
 	if xPreHigressVersion == "" {
 		return deployment
@@ -295,32 +282,52 @@ func IsSupportMultiVersion(grayConfig config.GrayConfig) bool {
 	return false
 }
 
-// FilterMultiVersionGrayRule 过滤多版本灰度规则
-func FilterMultiVersionGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, requestPath string) *config.Deployment {
-	// 首先根据灰度键值获取当前部署
-	currentDeployment := FilterGrayRule(grayConfig, grayKeyValue)
-
-	// 创建一个新的部署对象，初始化版本为当前部署的版本
-	deployment := &config.Deployment{
-		Version: currentDeployment.Version,
+func GetConditionRules(rules []*config.GrayRule, grayKeyValue string, cookie string) string {
+	ruleMaps := map[string]bool{}
+	for _, grayRule := range rules {
+		if grayRule.GrayKeyValue != nil && len(grayRule.GrayKeyValue) > 0 && grayKeyValue != "" {
+			ruleMaps[grayRule.Name] = ContainsValue(grayRule.GrayKeyValue, grayKeyValue)
+			continue
+		} else if grayRule.GrayTagKey != "" && grayRule.GrayTagValue != nil && len(grayRule.GrayTagValue) > 0 {
+			grayTagValue := GetCookieValue(cookie, grayRule.GrayTagKey)
+			ruleMaps[grayRule.Name] = ContainsValue(grayRule.GrayTagValue, grayTagValue)
+			continue
+		} else {
+			ruleMaps[grayRule.Name] = false
+		}
 	}
+	jsonBytes, err := json.Marshal(ruleMaps)
+	if err != nil {
+		return ""
+	}
+	return string(jsonBytes)
+}
 
-	// 对版本谓词的键进行排序
-	keys := SortKeysByLengthAndLexicographically(currentDeployment.VersionPredicates)
-
-	// 遍历排序后的键
-	for _, prefix := range keys {
-		// 如果请求路径以当前前缀开头
-		if strings.HasPrefix(requestPath, prefix) {
-			deployment.Version = currentDeployment.VersionPredicates[prefix]
-			return deployment
-		}
+func GetGrayWeightUniqueId(cookie string, uniqueGrayTag string) string {
+	uniqueId := GetCookieValue(cookie, uniqueGrayTag)
+	if uniqueId == "" {
+		uniqueId = strings.ReplaceAll(uuid.NewString(), "-", "")
 	}
-	return deployment
+	return uniqueId
 }
 
 // FilterGrayRule 过滤灰度规则
-func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment {
+func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, cookie string) *config.Deployment {
+	if grayConfig.GrayWeight > 0 {
+		uniqueId := GetGrayWeightUniqueId(cookie, grayConfig.UniqueGrayTag)
+		// 计算哈希后取模
+		mod := crc32.ChecksumIEEE([]byte(uniqueId)) % 100
+		isGray := mod < uint32(grayConfig.GrayWeight)
+		if isGray {
+			for _, deployment := range grayConfig.GrayDeployments {
+				if deployment.Enabled && deployment.Weight > 0 {
+					return deployment
+				}
+			}
+		}
+		return grayConfig.BaseDeployment
+	}
+
 	for _, deployment := range grayConfig.GrayDeployments {
 		grayRule := GetRule(grayConfig.Rules, deployment.Name)
 		// 首先：先校验用户名单ID
@@ -331,8 +338,7 @@ func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.
 		}
 		//	第二：校验Cookie中的 GrayTagKey
 		if grayRule.GrayTagKey != "" && grayRule.GrayTagValue != nil && len(grayRule.GrayTagValue) > 0 {
-			cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie")
-			grayTagValue := ExtractCookieValueByKey(cookieStr, grayRule.GrayTagKey)
+			grayTagValue := GetCookieValue(cookie, grayRule.GrayTagKey)
 			if ContainsValue(grayRule.GrayTagValue, grayTagValue) {
 				return deployment
 			}
@@ -341,46 +347,37 @@ func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.
 	return grayConfig.BaseDeployment
 }
 
-func FilterGrayWeight(grayConfig *config.GrayConfig, preVersion string, preUniqueClientId string, uniqueClientId string) *config.Deployment {
-	// 如果没有灰度权重，直接返回基础版本
-	if grayConfig.TotalGrayWeight == 0 {
-		return grayConfig.BaseDeployment
-	}
+// FilterMultiVersionGrayRule 过滤多版本灰度规则
+func FilterMultiVersionGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, cookie string, requestPath string) *config.Deployment {
+	// 首先根据灰度键值获取当前部署
+	currentDeployment := FilterGrayRule(grayConfig, grayKeyValue, cookie)
 
-	deployments := append(grayConfig.GrayDeployments, grayConfig.BaseDeployment)
-	LogInfof("preVersion: %s, preUniqueClientId: %s, uniqueClientId: %s", preVersion, preUniqueClientId, uniqueClientId)
-	// 用户粘滞，确保每个用户每次访问的都是走同一版本
-	if preVersion != "" && uniqueClientId == preUniqueClientId {
-		for _, deployment := range deployments {
-			if deployment.Version == preVersion {
-				return deployment
-			}
-		}
+	// 创建一个新的部署对象，初始化版本为当前部署的版本
+	deployment := &config.Deployment{
+		Version: currentDeployment.Version,
 	}
 
-	totalWeight := 100
-	// 如果总权重小于100，则将基础版本也加入到总版本列表中
-	if grayConfig.TotalGrayWeight <= totalWeight {
-		grayConfig.BaseDeployment.Weight = 100 - grayConfig.TotalGrayWeight
-	} else {
-		totalWeight = grayConfig.TotalGrayWeight
-	}
-	rand.Seed(time.Now().UnixNano())
-	randWeight := rand.Intn(totalWeight)
-	sumWeight := 0
-	for _, deployment := range deployments {
-		sumWeight += deployment.Weight
-		if randWeight < sumWeight {
+	// 对版本谓词的键进行排序
+	keys := SortKeysByLengthAndLexicographically(currentDeployment.VersionPredicates)
+
+	// 遍历排序后的键
+	for _, prefix := range keys {
+		// 如果请求路径以当前前缀开头
+		if strings.HasPrefix(requestPath, prefix) {
+			deployment.Version = currentDeployment.VersionPredicates[prefix]
 			return deployment
 		}
 	}
-	return nil
+	return deployment
 }
 
 // InjectContent 用于将内容注入到 HTML 文档的指定位置
-func InjectContent(originalHtml string, injectionConfig *config.Injection) string {
-
-	headInjection := strings.Join(injectionConfig.Head, "\n")
+func InjectContent(originalHtml string, injectionConfig *config.Injection, globalConfigValue string) string {
+	heads := injectionConfig.Head
+	if globalConfigValue != "" {
+		heads = append([]string{globalConfigValue}, injectionConfig.Head...)
+	}
+	headInjection := strings.Join(heads, "\n")
 	bodyFirstInjection := strings.Join(injectionConfig.Body.First, "\n")
 	bodyLastInjection := strings.Join(injectionConfig.Body.Last, "\n")
 
@@ -401,3 +398,14 @@ func InjectContent(originalHtml string, injectionConfig *config.Injection) strin
 
 	return modifiedHtml
 }
+
+func FixLocalStorageKey(newHtml string, localStorageGrayKey string) string {
+	if localStorageGrayKey != "" {
+		localStr := strings.ReplaceAll(`<script>
+	!function(){var o,e,n="@@X_GRAY_KEY",t=document.cookie.split("; ").filter(function(o){return 0===o.indexOf(n+"=")});try{"undefined"!=typeof localStorage&&null!==localStorage&&(o=localStorage.getItem(n),e=0<t.length?decodeURIComponent(t[0].split("=")[1]):null,o)&&o.indexOf("=")<0&&e&&e!==o&&(document.cookie=n+"="+encodeURIComponent(o)+"; path=/;",window.location.reload())}catch(o){}}();
+	</script>
+	`, "@@X_GRAY_KEY", localStorageGrayKey)
+		newHtml = strings.ReplaceAll(newHtml, "<body>", "<body>\n"+localStr)
+	}
+	return newHtml
+}
diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go
index 5fb69bb364..831ef55db4 100644
--- a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go
+++ b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go
@@ -3,12 +3,14 @@ package util
 import (
 	"testing"
 
+	"github.com/bmatcuk/doublestar/v4"
+
 	"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config"
 	"github.com/stretchr/testify/assert"
 	"github.com/tidwall/gjson"
 )
 
-func TestExtractCookieValueByKey(t *testing.T) {
+func TestGetCookieValue(t *testing.T) {
 	var tests = []struct {
 		cookie, cookieKey, output string
 	}{
@@ -21,7 +23,7 @@ func TestExtractCookieValueByKey(t *testing.T) {
 	for _, test := range tests {
 		testName := test.cookie
 		t.Run(testName, func(t *testing.T) {
-			output := ExtractCookieValueByKey(test.cookie, test.cookieKey)
+			output := GetCookieValue(test.cookie, test.cookieKey)
 			assert.Equal(t, test.output, output)
 		})
 	}
@@ -106,7 +108,7 @@ func TestPrefixFileRewrite(t *testing.T) {
 	}
 }
 
-func TestIsPageRequest(t *testing.T) {
+func TestCheckIsHtmlRequest(t *testing.T) {
 	var tests = []struct {
 		p      string
 		output bool
@@ -121,45 +123,44 @@ func TestIsPageRequest(t *testing.T) {
 	for _, test := range tests {
 		testPath := test.p
 		t.Run(testPath, func(t *testing.T) {
-			output := IsPageRequest(testPath)
+			output := CheckIsHtmlRequest(testPath)
 			assert.Equal(t, test.output, output)
 		})
 	}
 }
-
-func TestFilterGrayWeight(t *testing.T) {
+func TestReplaceHtml(t *testing.T) {
 	var tests = []struct {
 		name  string
 		input string
 	}{
-		{"demo", `{"grayKey":"userId","rules":[{"name":"inner-user","grayKeyValue":["00000001","00000005"]},{"name":"beta-user","grayKeyValue":["noah","00000003"],"grayTagKey":"level","grayTagValue":["level3","level5"]}],"rewrite":{"host":"frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com","notFoundUri":"/mfe/app1/dev/404.html","indexRouting":{"/app1":"/mfe/app1/{version}/index.html","/":"/mfe/app1/{version}/index.html"},"fileRouting":{"/":"/mfe/app1/{version}","/app1":"/mfe/app1/{version}"}},"baseDeployment":{"version":"dev"},"grayDeployments":[{"name":"beta-user","version":"0.0.1","backendVersion":"beta","enabled":true,"weight":50}]}`},
+		{"demo", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n   <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n<body>\n\t测试替换html版本\n\t<br />\n\t版本: {version}\n\t<br />\n\t<script src=\"./{version}/a.js\"></script>\n</body>\n</html>"}`},
+		{"demo-noBody", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n   <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n</html>"}`},
 	}
 	for _, test := range tests {
 		testName := test.name
 		t.Run(testName, func(t *testing.T) {
 			grayConfig := &config.GrayConfig{}
 			config.JsonToGrayConfig(gjson.Parse(test.input), grayConfig)
-			result := FilterGrayWeight(grayConfig, "base", "1.0.1", "192.168.1.1")
+			result := InjectContent(grayConfig.Html, grayConfig.Injection, "")
 			t.Logf("result-----: %v", result)
 		})
 	}
 }
 
-func TestReplaceHtml(t *testing.T) {
+func TestIsIndexRequest(t *testing.T) {
 	var tests = []struct {
-		name  string
-		input string
+		name   string
+		input  string
+		output bool
 	}{
-		{"demo", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n   <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n<body>\n\t测试替换html版本\n\t<br />\n\t版本: {version}\n\t<br />\n\t<script src=\"./{version}/a.js\"></script>\n</body>\n</html>"}`},
-		{"demo-noBody", `{"injection":{"head":["<script>console.log('Head')</script>"],"body":{"first":["<script>console.log('BodyFirst')</script>"],"last":["<script>console.log('BodyLast')</script>"]},"last":["<script>console.log('BodyLast')</script>"]},"html": "<!DOCTYPE html>\n   <html lang=\"zh-CN\">\n<head>\n<title>app1</title>\n<meta charset=\"utf-8\" />\n</head>\n</html>"}`},
+		{"/api/user.json", "/api/**", true},
+		{"/api/blade-auth/oauth/captcha", "/api/**", true},
 	}
 	for _, test := range tests {
 		testName := test.name
 		t.Run(testName, func(t *testing.T) {
-			grayConfig := &config.GrayConfig{}
-			config.JsonToGrayConfig(gjson.Parse(test.input), grayConfig)
-			result := InjectContent(grayConfig.Html, grayConfig.Injection)
-			t.Logf("result-----: %v", result)
+			matchResult, _ := doublestar.Match(test.input, testName)
+			assert.Equal(t, test.output, matchResult)
 		})
 	}
 }


Reviews:
user: login: CH3CHO
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-06T05:52:10Z
user: login: heimanba
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T16:38:41Z
user: login: heimanba
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T16:40:00Z
user: login: heimanba
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T17:10:46Z
user: login: CH3CHO
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T02:03:42Z

Review comments:
diff_hunk: @@ -70,17 +71,28 @@ description: 前端灰度插件配置参考
 | `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `name` | string | 必填   | -   | 规则名称和`rules[].name`关联 |
 | `enabled`  | boolean   | 必填   | -   | 是否启动当前灰度规则                                      |
-| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如`50`。注意：灰度规则权重总和不能超过100，如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
-> 为了实现按比例（weight） 进行灰度发布，并确保用户粘滞，我们需要确认客户端的唯一性。如果配置了 grayKey，则将其用作唯一标识；如果未配置 grayKey，则使用客户端的访问 IP 地址作为唯一标识。
+| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如50。 |
+>按照比例灰度注意下面几点:
+> 1. 如果同时配置了`按用户灰度`以及`按比例灰度`，按`比例灰度`优先生效
+> 2. 采用客户端设备标识符的哈希摘要机制实现流量比例控制，其唯一性判定逻辑遵循以下原则：自动生成全局唯一标识符（UUID）作为设备指纹，可以通过`uniqueGrayTag`配置`cookie`的key值，并通过SHA-256哈希算法生成对应灰度判定基准值。
 
 
 `injection`字段配置说明：
 
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
+| `globalConfig`  | object | 非必填   | -   | 注入到HTML首页的全局变量 |
 | `head`  | array of string | 非必填   | -   | 注入head信息，比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
 | `body`  | object | 非必填   | -   | 注入Body |
 
+`injection.globalConfig`字段配置说明：
+| 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
+|--------|--------|------|-----|-------------------------------------------------|
+| `key`  | string | 非必填   |  HIGRESS_CONSOLE_CONFIG  | 注入到windwow全局变量的key值 |
path: plugins/wasm-go/extensions/frontend-gray/README.md
user: login: CH3CHO
body: window 拼写错误
created_at: 2025-04-06T05:42:30Z
author_association: COLLABORATOR
diff_hunk: @@ -70,17 +71,28 @@ description: 前端灰度插件配置参考
 | `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `name` | string | 必填   | -   | 规则名称和`rules[].name`关联 |
 | `enabled`  | boolean   | 必填   | -   | 是否启动当前灰度规则                                      |
-| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如`50`。注意：灰度规则权重总和不能超过100，如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
-> 为了实现按比例（weight） 进行灰度发布，并确保用户粘滞，我们需要确认客户端的唯一性。如果配置了 grayKey，则将其用作唯一标识；如果未配置 grayKey，则使用客户端的访问 IP 地址作为唯一标识。
+| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如50。 |
+>按照比例灰度注意下面几点:
+> 1. 如果同时配置了`按用户灰度`以及`按比例灰度`，按`比例灰度`优先生效
+> 2. 采用客户端设备标识符的哈希摘要机制实现流量比例控制，其唯一性判定逻辑遵循以下原则：自动生成全局唯一标识符（UUID）作为设备指纹，可以通过`uniqueGrayTag`配置`cookie`的key值，并通过SHA-256哈希算法生成对应灰度判定基准值。
 
 
 `injection`字段配置说明：
 
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
+| `globalConfig`  | object | 非必填   | -   | 注入到HTML首页的全局变量 |
 | `head`  | array of string | 非必填   | -   | 注入head信息，比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
 | `body`  | object | 非必填   | -   | 注入Body |
 
+`injection.globalConfig`字段配置说明：
+| 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
+|--------|--------|------|-----|-------------------------------------------------|
+| `key`  | string | 非必填   |  HIGRESS_CONSOLE_CONFIG  | 注入到windwow全局变量的key值 |
+| `featureKey`  | string | 非必填   | FEATURE_STATUS   | 关于`rules`相关规则的命中情况，返回实例`{"beta-user":true,"inner-user":false}` |
path: plugins/wasm-go/extensions/frontend-gray/README.md
user: login: CH3CHO
body: 这两个字段名字需要做一些格式限定和检查吗，避免生成的数据在格式上出现问题。
created_at: 2025-04-06T05:47:21Z
author_association: COLLABORATOR
diff_hunk: @@ -29,34 +26,26 @@ func main() {
 func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error {
path: plugins/wasm-go/extensions/frontend-gray/main.go
user: login: CH3CHO
body: 上面几个 deprecated 的地方可以顺便改掉

![image](https://github.com/user-attachments/assets/fe72b94d-e0eb-4461-8070-b6bac035ce03)

created_at: 2025-04-06T05:51:38Z
author_association: COLLABORATOR
diff_hunk: @@ -70,17 +71,28 @@ description: 前端灰度插件配置参考
 | `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `name` | string | 必填   | -   | 规则名称和`rules[].name`关联 |
 | `enabled`  | boolean   | 必填   | -   | 是否启动当前灰度规则                                      |
-| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如`50`。注意：灰度规则权重总和不能超过100，如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
-> 为了实现按比例（weight） 进行灰度发布，并确保用户粘滞，我们需要确认客户端的唯一性。如果配置了 grayKey，则将其用作唯一标识；如果未配置 grayKey，则使用客户端的访问 IP 地址作为唯一标识。
+| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如50。 |
+>按照比例灰度注意下面几点:
+> 1. 如果同时配置了`按用户灰度`以及`按比例灰度`，按`比例灰度`优先生效
+> 2. 采用客户端设备标识符的哈希摘要机制实现流量比例控制，其唯一性判定逻辑遵循以下原则：自动生成全局唯一标识符（UUID）作为设备指纹，可以通过`uniqueGrayTag`配置`cookie`的key值，并通过SHA-256哈希算法生成对应灰度判定基准值。
 
 
 `injection`字段配置说明：
 
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
+| `globalConfig`  | object | 非必填   | -   | 注入到HTML首页的全局变量 |
 | `head`  | array of string | 非必填   | -   | 注入head信息，比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
 | `body`  | object | 非必填   | -   | 注入Body |
 
+`injection.globalConfig`字段配置说明：
+| 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
+|--------|--------|------|-----|-------------------------------------------------|
+| `key`  | string | 非必填   |  HIGRESS_CONSOLE_CONFIG  | 注入到windwow全局变量的key值 |
path: plugins/wasm-go/extensions/frontend-gray/README.md
user: login: heimanba
body: > window 拼写错误

fixed
created_at: 2025-04-10T16:38:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -70,17 +71,28 @@ description: 前端灰度插件配置参考
 | `backendVersion`  | string | 必填   | -   | 后端灰度版本，配合`key`为`${backendGrayTag}`，写入cookie中 |
 | `name` | string | 必填   | -   | 规则名称和`rules[].name`关联 |
 | `enabled`  | boolean   | 必填   | -   | 是否启动当前灰度规则                                      |
-| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如`50`。注意：灰度规则权重总和不能超过100，如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
-> 为了实现按比例（weight） 进行灰度发布，并确保用户粘滞，我们需要确认客户端的唯一性。如果配置了 grayKey，则将其用作唯一标识；如果未配置 grayKey，则使用客户端的访问 IP 地址作为唯一标识。
+| `weight`  | int   | 非必填   | -   | 按照比例灰度，比如50。 |
+>按照比例灰度注意下面几点:
+> 1. 如果同时配置了`按用户灰度`以及`按比例灰度`，按`比例灰度`优先生效
+> 2. 采用客户端设备标识符的哈希摘要机制实现流量比例控制，其唯一性判定逻辑遵循以下原则：自动生成全局唯一标识符（UUID）作为设备指纹，可以通过`uniqueGrayTag`配置`cookie`的key值，并通过SHA-256哈希算法生成对应灰度判定基准值。
 
 
 `injection`字段配置说明：
 
 | 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
 |--------|--------|------|-----|-------------------------------------------------|
+| `globalConfig`  | object | 非必填   | -   | 注入到HTML首页的全局变量 |
 | `head`  | array of string | 非必填   | -   | 注入head信息，比如`<link rel="stylesheet" href="https://cdn.example.com/styles.css">` |
 | `body`  | object | 非必填   | -   | 注入Body |
 
+`injection.globalConfig`字段配置说明：
+| 名称     | 数据类型   | 填写要求 | 默认值 | 描述                                              |
+|--------|--------|------|-----|-------------------------------------------------|
+| `key`  | string | 非必填   |  HIGRESS_CONSOLE_CONFIG  | 注入到windwow全局变量的key值 |
+| `featureKey`  | string | 非必填   | FEATURE_STATUS   | 关于`rules`相关规则的命中情况，返回实例`{"beta-user":true,"inner-user":false}` |
path: plugins/wasm-go/extensions/frontend-gray/README.md
user: login: heimanba
body: > 这两个字段名字需要做一些格式限定和检查吗，避免生成的数据在格式上出现问题。

通过下面方法进行限制了下
```
func isValidName(s string) bool {
	// 定义一个正则表达式，匹配字母、数字和下划线
	re := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
	return re.MatchString(s)
}
```
created_at: 2025-04-10T16:40:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,34 +26,26 @@ func main() {
 func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error {
path: plugins/wasm-go/extensions/frontend-gray/main.go
user: login: heimanba
body: > 上面几个 deprecated 的地方可以顺便改掉
> 
> ![image](https://private-user-images.githubusercontent.com/2909796/430680714-fe72b94d-e0eb-4461-8070-b6bac035ce03.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDQzMDUzMjksIm5iZiI6MTc0NDMwNTAyOSwicGF0aCI6Ii8yOTA5Nzk2LzQzMDY4MDcxNC1mZTcyYjk0ZC1lMGViLTQ0NjEtODA3MC1iNmJhYzAzNWNlMDMucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDQxMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA0MTBUMTcxMDI5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OGI2YjkzZWQ2MWI3ZjI3ZmZmNTYyYzdlZmJiZmYwNGUzZjhiYjUyYzNhYzAwMjYzNDE2NTdjMjY4Nzk2YzIxOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.R_Lca66K34Pg0zNg_KmHGsCpdApd4KQqmeNk9aTDfWI)

已经修复
created_at: 2025-04-10T17:10:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: CH3CHO
created_at: 2025-04-02T12:57:19Z
body: 请先解决冲突。
author_association: COLLABORATOR
user: login: heimanba
created_at: 2025-04-03T01:22:55Z
body: > 请先解决冲突。

 已解决
author_association: CONTRIBUTOR
user: login: codecov-commenter
created_at: 2025-04-03T01:24:53Z
body: ## [Codecov](https://app.codecov.io/gh/alibaba/higress/pull/2011?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=alibaba) Report
:white_check_mark: All modified and coverable lines are covered by tests.

:loudspeaker: Thoughts on this report? [Let us know!](https://github.com/codecov/feedback/issues/255)
author_association: NONE