diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md new file mode 100644 index 000000000..283785807 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -0,0 +1,103 @@ +# frontend-gray 前端灰度插件 +## 功能说明 +`frontend-gray`插件实现了前端用户灰度的的功能,通过此插件,不但可以用于业务`A/B实验`,同时通过`可灰度`配合`可监控`,`可回滚`策略保证系统发布运维的稳定性。 + +## 配置字段 +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|----------------|--------------|------|-----|-----------------------------------------------------------------------------------| +| `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 | +| `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` | +| `rules` | array of object | 非必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 | +| `baseDeployment` | object | 非必填 | - | 配置Base基线规则的配置 | +| `grayDeployments` | array of object | 非必填 | - | 配置Gray灰度的生效规则,以及生效版本 | + +`rules`字段配置说明: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|----------------|--------------|------|-----|-----------------------------------------------------------------------------------| +| `name` | string | 必填 | - | 规则名称唯一标识,和`deploy.gray[].name`进行关联生效 | +| `grayKeyValue` | array of string | 非必填 | - | 用户ID 白名单列表 | +| `grayTagKey` | string | 非必填 | - | 用户分类打标的标签key值,来自Cookie | +| `grayTagValue` | array of string | 非必填 | - | 用户分类打标的标签value值,来自Cookie | + +`baseDeployment`字段配置说明: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|----------------|--------------|------|-----|-----------------------------------------------------------------------------------| +| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 | + +`grayDeployments`字段配置说明: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|--------|--------|------|-----|----------------------------| +| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本 | +| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, | +| `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 | + +## 配置示例 +### 基础配置 +```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 +``` + + +cookie中的用户唯一标识为 `userid`,当前灰度规则配置了`beta-user`的规则。 + +当满足下面调试的时候,会使用`version: gray`版本 +- cookie中`userid`等于`00000002`或者`00000003` +- cookie中`level`等于`level3`或者`level5`的用户 + +否则使用`version: base`版本 + +### 用户信息存在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 +``` + +cookie存在`appInfo`的JSON数据,其中包含`userId`字段为当前的唯一标识 +当前灰度规则配置了`beta-user`的规则。 +当满足下面调试的时候,会使用`version: gray`版本 +- cookie中`userid`等于`00000002`或者`00000003` +- cookie中`level`等于`level3`或者`level5`的用户 + +否则使用`version: base`版本 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go new file mode 100644 index 000000000..ce87b816d --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -0,0 +1,82 @@ +package config + +import ( + "strconv" + + "github.com/tidwall/gjson" +) + +type GrayRule struct { + Name string + GrayKeyValue []interface{} + GrayTagKey string + GrayTagValue []interface{} +} + +type BaseDeployment struct { + Name string + Version string +} + +type GrayDeployments struct { + Name string + Version string + Enabled bool +} + +type GrayConfig struct { + GrayKey string + GraySubKey string + Rules []*GrayRule + BaseDeployment *BaseDeployment + GrayDeployments []*GrayDeployments +} + +func interfacesFromJSONResult(results []gjson.Result) []interface{} { + var interfaces []interface{} + for _, result := range results { + switch v := result.Value().(type) { + case float64: + // 当 v 是 float64 时,将其转换为字符串 + interfaces = append(interfaces, strconv.FormatFloat(v, 'f', -1, 64)) + default: + // 其它类型不改变,直接追加 + interfaces = append(interfaces, v) + } + } + return interfaces +} + +func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { + // 解析 GrayKey + grayConfig.GrayKey = json.Get("grayKey").String() + grayConfig.GraySubKey = json.Get("graySubKey").String() + + // 解析 Rules + rules := json.Get("rules").Array() + for _, rule := range rules { + grayRule := GrayRule{ + Name: rule.Get("name").String(), + GrayKeyValue: interfacesFromJSONResult(rule.Get("grayKeyValue").Array()), // 使用辅助函数将 []gjson.Result 转换为 []interface{} + GrayTagKey: rule.Get("grayTagKey").String(), + GrayTagValue: interfacesFromJSONResult(rule.Get("grayTagValue").Array()), + } + grayConfig.Rules = append(grayConfig.Rules, &grayRule) + } + + // 解析 deploy + baseDeployment := json.Get("baseDeployment") + grayDeployments := json.Get("grayDeployments").Array() + + grayConfig.BaseDeployment = &BaseDeployment{ + Name: baseDeployment.Get("name").String(), + Version: baseDeployment.Get("version").String(), + } + for _, item := range grayDeployments { + grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &GrayDeployments{ + Name: item.Get("name").String(), + Version: item.Get("version").String(), + Enabled: item.Get("enabled").Bool(), + }) + } +} diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config_test.go b/plugins/wasm-go/extensions/frontend-gray/config/config_test.go new file mode 100644 index 000000000..9983c829e --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/config/config_test.go @@ -0,0 +1,27 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" +) + +func TestJsonToGrayConfig(t *testing.T) { + allConfigData := `{"grayKey":"userid","rules":[{"name":"inner-user","grayKeyValue":["00000001","00000005"]},{"name":"beta-user","grayKeyValue":["00000002","00000003"],"grayTagKey":"level","grayTagValue":["level3","level5"]}],"deploy":{"base":{"version":"base"},"gray":[{"name":"beta-user","version":"gray","enabled":true}]}}` + var tests = []struct { + testName string + grayKey string + json string + }{ + {"完整的数据", "userid", allConfigData}, + } + for _, test := range tests { + testName := test.testName + t.Run(testName, func(t *testing.T) { + var grayConfig = &GrayConfig{} + JsonToGrayConfig(gjson.Parse(test.json), grayConfig) + assert.Equal(t, test.grayKey, grayConfig.GrayKey) + }) + } +} diff --git a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml new file mode 100644 index 000000000..63c6ec0d2 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml @@ -0,0 +1,102 @@ +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + scheme_header_transformation: + scheme_to_overwrite: https + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: httpbin + http_filters: + - name: wasmdemo + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + name: wasmdemo + vm_config: + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: ./main.wasm + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { + "grayKey": "UserInfo", + "graySubKey": "userCode", + "rules": [ + { + "name": "inner-user", + "grayKeyValue": [ + "00000001", + "00000005" + ] + }, + { + "name": "beta-user", + "grayKeyValue": [ + "noah", + "00000003" + ], + "grayTagKey": "level", + "grayTagValue": [ + "level3", + "level5" + ] + } + ], + "baseDeployment": { + "version": "base" + }, + "grayDeployments": [ + { + "name": "beta-user", + "version": "gray", + "enabled": true + } + ] + } + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: httpbin + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: httpbin + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: httpbin.org + port_value: 80 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/frontend-gray/go.mod b/plugins/wasm-go/extensions/frontend-gray/go.mod new file mode 100644 index 000000000..37cd97090 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/go.mod @@ -0,0 +1,24 @@ +module github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray + +go 1.18 + +replace github.com/alibaba/higress/plugins/wasm-go => ../.. + +require ( + github.com/alibaba/higress/plugins/wasm-go v0.0.0-20240531060402-2807ddfbb79e + github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc + github.com/stretchr/testify v1.8.4 + github.com/tidwall/gjson v1.17.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect + github.com/magefile/mage v1.14.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/resp v0.1.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/plugins/wasm-go/extensions/frontend-gray/go.sum b/plugins/wasm-go/extensions/frontend-gray/go.sum new file mode 100644 index 000000000..d6289a648 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/go.sum @@ -0,0 +1,32 @@ +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20240531060402-2807ddfbb79e h1:0b2UXrEpotHwWgwvgvkXnyKWuxTXtzfKu6c2YpRV+zw= +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20240531060402-2807ddfbb79e/go.mod h1:10jQXKsYFUF7djs+Oy7t82f4dbie9pISfP9FJwpPLuk= +github.com/alibaba/higress/plugins/wasm-go v1.3.5 h1:VOLL3m442IHCSu8mR5AZ4sc6LVT9X0w1hdqDI7oB9jY= +github.com/alibaba/higress/plugins/wasm-go v1.3.5/go.mod h1:kr3V9Ntbspj1eSrX8rgjBsdMXkGupYEf+LM72caGPQc= +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/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= +github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226065437-8f7a0b3c9071 h1:STb5rOHRZOzoiAa+gTz2LFqO1nYj7U/1eIVUJJadU4A= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226065437-8f7a0b3c9071/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE= +github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go new file mode 100644 index 000000000..5d4ecaed4 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "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/wrapper" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + "github.com/tidwall/gjson" +) + +func main() { + wrapper.SetCtx( + "frontend-gray", + wrapper.ParseConfigBy(parseConfig), + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + ) +} + +func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error { + // 解析json 为GrayConfig + config.JsonToGrayConfig(json, grayConfig) + return nil +} + +// FilterGrayRule 过滤灰度规则 +func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, log wrapper.Log) *config.GrayDeployments { + for _, grayDeployment := range grayConfig.GrayDeployments { + if !grayDeployment.Enabled { + // 跳过Enabled=false + continue + } + grayRule := util.GetRule(grayConfig.Rules, grayDeployment.Name) + // 首先:先校验用户名单ID + if grayRule.GrayKeyValue != nil && len(grayRule.GrayKeyValue) > 0 && grayKeyValue != "" { + if util.Contains(grayRule.GrayKeyValue, grayKeyValue) { + log.Infof("x-mse-tag: %s, grayKeyValue: %s", grayDeployment.Version, grayKeyValue) + return grayDeployment + } + } + // 第二:校验Cookie中的 GrayTagKey + if grayRule.GrayTagKey != "" && grayRule.GrayTagValue != nil && len(grayRule.GrayTagValue) > 0 { + cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie") + grayTagValue := util.GetValueByCookie(cookieStr, grayRule.GrayTagKey) + if util.Contains(grayRule.GrayTagValue, grayTagValue) { + log.Infof("x-mse-tag: %s, grayTag: %s=%s", grayDeployment.Version, grayRule.GrayTagKey, grayTagValue) + return grayDeployment + } + } + } + log.Infof("x-mse-tag: %s, grayKeyValue: %s", grayConfig.BaseDeployment.Version, grayKeyValue) + return nil +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action { + // 优先从cookie中获取,如果拿不到再从header中获取 + cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie") + grayHeaderKey, _ := proxywasm.GetHttpRequestHeader(grayConfig.GrayKey) + grayKeyValue := util.GetValueByCookie(cookieStr, grayConfig.GrayKey) + proxywasm.RemoveHttpRequestHeader("Accept-Encoding") + // 优先从Cookie中获取,否则从header中获取 + if grayKeyValue == "" { + grayKeyValue = grayHeaderKey + } + // 如果有子key, 尝试从子key中获取值 + if grayConfig.GraySubKey != "" { + subKeyValue := util.GetBySubKey(grayKeyValue, grayConfig.GraySubKey) + if subKeyValue != "" { + grayKeyValue = subKeyValue + } + } + grayDeployment := FilterGrayRule(&grayConfig, grayKeyValue, log) + if grayDeployment != nil { + proxywasm.AddHttpRequestHeader("x-mse-tag", grayDeployment.Version) + } + 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 new file mode 100644 index 000000000..9faa96082 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -0,0 +1,70 @@ +package util + +import ( + "net/url" + "strings" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config" + + "github.com/tidwall/gjson" +) + +// GetValueByCookie 根据 cookieStr 和 cookieName 获取 cookie 值 +func GetValueByCookie(cookieStr string, cookieName string) string { + if cookieStr == "" { + return "" + } + cookies := strings.Split(cookieStr, ";") + curCookieName := cookieName + "=" + var foundCookieValue string + var found bool + // 遍历找到 cookie 对并处理 + for _, cookie := range cookies { + cookie = strings.TrimSpace(cookie) // 清理空白符 + if strings.HasPrefix(cookie, curCookieName) { + foundCookieValue = cookie[len(curCookieName):] + found = true + break + } + } + if !found { + return "" + } + return foundCookieValue +} + +// contains 检查切片 slice 中是否含有元素 value。 +func Contains(slice []interface{}, value string) bool { + for _, item := range slice { + if item == value { + return true + } + } + return false +} + +func GetRule(rules []*config.GrayRule, name string) *config.GrayRule { + for _, rule := range rules { + if rule.Name == name { + return rule + } + } + return nil +} + +func GetBySubKey(grayInfoStr string, graySubKey string) string { + // 首先对 URL 编码的字符串进行解码 + jsonStr, err := url.QueryUnescape(grayInfoStr) + if err != nil { + return "" + } + // 使用 gjson 从 JSON 字符串中提取 graySubKey 对应的值 + value := gjson.Get(jsonStr, graySubKey) + + // 检查所提取的值是否存在 + if !value.Exists() { + return "" + } + // 返回字符串形式的值 + return value.String() +} diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go new file mode 100644 index 000000000..13e936794 --- /dev/null +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go @@ -0,0 +1,42 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetValueByCookie(t *testing.T) { + var tests = []struct { + cookie, cookieKey, output string + }{ + {"", "uid", ""}, + {`cna=pf_9be76347560439f3b87daede1b485e37; uid=111`, "uid", "111"}, + {`cna=pf_9be76347560439f3b87daede1b485e37; userid=222`, "userid", "222"}, + {`uid=333`, "uid", "333"}, + {`cna=pf_9be76347560439f3b87daede1b485e37;`, "uid", ""}, + } + for _, test := range tests { + testName := test.cookie + t.Run(testName, func(t *testing.T) { + output := GetValueByCookie(test.cookie, test.cookieKey) + assert.Equal(t, test.output, output) + }) + } +} + +func TestDecodeJsonCookie(t *testing.T) { + var tests = []struct { + userInfoStr, grayJsonKey, output string + }{ + {"{%22password%22:%22$2a$10$YAvYjA6783YeCi44/M395udIZ4Ll2iyKkQCzePaYx5NNG/aIWgICG%22%2C%22username%22:%22%E8%B0%A2%E6%99%AE%E8%80%80%22%2C%22authorities%22:[]%2C%22accountNonExpired%22:true%2C%22accountNonLocked%22:true%2C%22credentialsNonExpired%22:true%2C%22enabledd%22:true%2C%22id%22:838925798835720200%2C%22mobile%22:%22%22%2C%22userCode%22:%22noah%22%2C%22userName%22:%22%E8%B0%A2%E6%99%AE%E8%80%80%22%2C%22orgId%22:10%2C%22ocId%22:87%2C%22userType%22:%22OWN%22%2C%22firstLogin%22:false%2C%22ownOrgId%22:null%2C%22clientCode%22:%22%22%2C%22clientType%22:null%2C%22country%22:%22UAE%22%2C%22isGuide%22:null%2C%22acctId%22:null%2C%22userToken%22:null%2C%22deviceId%22:%223a47fec00a59d140%22%2C%22ocCode%22:%2299990002%22%2C%22secondType%22:%22dtl%22%2C%22vendorCode%22:%2210000001%22%2C%22status%22:%22ACTIVE%22%2C%22isDelete%22:false%2C%22email%22:%22%22%2C%22deleteStatus%22:null%2C%22deleteRequestDate%22:null%2C%22wechatId%22:null%2C%22userMfaInfoDTO%22:{%22checkMfa%22:false%2C%22checkSuccess%22:false%2C%22mobile%22:null%2C%22email%22:null%2C%22wechatId%22:null%2C%22totpSecret%22:null}}", + "userCode", "noah"}, + } + for _, test := range tests { + testName := test.userInfoStr + t.Run(testName, func(t *testing.T) { + output := GetBySubKey(test.userInfoStr, test.grayJsonKey) + assert.Equal(t, test.output, output) + }) + } +}