-
Notifications
You must be signed in to change notification settings - Fork 375
/
gray_level.go
174 lines (156 loc) · 5.02 KB
/
gray_level.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package publish_item
import (
"fmt"
"hash/fnv"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/modules/dicehub/dbclient"
"github.com/erda-project/erda/pkg/strutil"
)
const (
headerXFF = "X-Forwarded-For"
cookieDiceMobilAppDistribution = "Dice-Mobil-App-Distribution" // 1, 2, 3, 4
cookieSplitter = ", "
)
// GrayDistribution 根据用户身份进行和灰度设置进行灰度分发
func (i *PublishItem) GrayDistribution(w http.ResponseWriter, r *http.Request, publisherItem dbclient.PublishItem,
distribution *apistructs.PublishItemDistributionData, mobileType apistructs.ResourceType, packageName string) error {
// 获取已发布版本
total, tmpVersions, err := i.db.GetPublicVersion(int64(publisherItem.ID), mobileType, packageName)
if err != nil {
return err
}
if total == 0 {
return nil
}
versions, err := discriminateReleaseAndBeta(total, tmpVersions)
if err != nil {
return err
}
releasVersion := versions[0].ToApiData()
distribution.Default = releasVersion
// 线上没有beta版本时,无需灰度
if total == 1 {
return nil
}
betaVersion := versions[1].ToApiData()
// 查询用户是否对当前发布内容已经是灰度
cookie, err := r.Cookie(cookieDiceMobilAppDistribution)
if err == nil {
alreadyGrayItems := strutil.Split(cookie.Value, cookieSplitter, true)
for _, itemID := range alreadyGrayItems {
if itemID == fmt.Sprintf("%d", publisherItem.ID) {
// 当前用户已经是灰度
handleGrayVersion(w, publisherItem.ID, distribution, releasVersion, betaVersion)
return nil
}
}
}
// 根据 X-Forwarded-For 判断用户是否需要灰度
ip := getRemoteIP(r)
if ip == nil {
return nil
}
// 满足灰度
hashStr := ip.String() + publisherItem.Name
if matchGrayMod(getHashedNum([]byte(hashStr)), 100, versions[1].GrayLevelPercent-1) {
handleGrayVersion(w, publisherItem.ID, distribution, releasVersion, betaVersion)
addGrayCookie(w, r, publisherItem.ID)
return nil
}
return nil
}
// handleGrayVersion 返回灰度版本
func handleGrayVersion(w http.ResponseWriter, publisherItemID uint64, distribution *apistructs.PublishItemDistributionData,
releaeVersion, betaVersion *apistructs.PublishItemVersion) {
// 设置默认版本为灰度版本
distribution.Default = betaVersion
// 重新处理版本列表
// 1. 添加默认版本
// 2. 添加非灰度版本
newVersionList := []*apistructs.PublishItemVersion{betaVersion}
for _, v := range distribution.Versions.List {
if v.ID != betaVersion.ID {
newVersionList = append(newVersionList, v)
}
}
distribution.Versions.List = newVersionList
}
func addGrayCookie(w http.ResponseWriter, r *http.Request, publisherID uint64) {
cookie, err := r.Cookie(cookieDiceMobilAppDistribution)
var grayItemIDs []string
if err == nil {
grayItemIDs = strutil.Split(cookie.Value, cookieSplitter, true)
// 若 value 异常,则全部清除
for _, id := range grayItemIDs {
if _, err := strconv.ParseInt(id, 10, 64); err != nil {
grayItemIDs = nil
break
}
}
}
grayItemIDs = strutil.DedupSlice(append(grayItemIDs, fmt.Sprintf("%d", publisherID)))
expire := time.Now().AddDate(0, 1, 0)
newCookie := http.Cookie{
Name: cookieDiceMobilAppDistribution,
Value: strutil.Join(grayItemIDs, cookieSplitter, true),
Path: "/",
Domain: r.Host,
Expires: expire,
}
http.SetCookie(w, &newCookie)
}
// getGrayVersion 查找灰度版本:default=false 中创建时间最新的版本
func getGrayVersion(versionData *apistructs.QueryPublishItemVersionData) *apistructs.PublishItemVersion {
if versionData == nil || versionData.List == nil {
return nil
}
var grayVersion *apistructs.PublishItemVersion
for _, v := range versionData.List {
if v.IsDefault {
continue
}
if grayVersion == nil {
grayVersion = v
continue
}
if v.CreatedAt.After(grayVersion.CreatedAt) {
grayVersion = v
}
}
return grayVersion
}
func getHashedNum(b []byte) int {
h := fnv.New32a()
h.Write(b)
return int(h.Sum32())
}
func matchGrayMod(hashedNum, mod, zeroToWhich int) bool {
return hashedNum%mod <= zeroToWhich
}
// getRemoteIP get ip from XFF header
// example: 122.235.82.217, 100.122.56.227, 9.117.157.128, 101.37.145.101, 10.118.183.0
func getRemoteIP(r *http.Request) net.IP {
// 1.1.1.1, 2,2,2,2
ips := strings.SplitN(r.Header.Get(headerXFF), ",", 2)
if len(ips) == 0 {
return nil
}
return net.ParseIP(ips[0])
}