Bläddra i källkod

Merge branch 'master' of https://jygit.jydev.jianyu360.cn/moapp/jybase into master

lianbingjie 5 månader sedan
förälder
incheckning
3e5094b7d0
63 ändrade filer med 3734 tillägg och 671 borttagningar
  1. 16 3
      common/common.go
  2. 48 3
      common/requtil.go
  3. 25 24
      encrypt/encryptarticle.go
  4. 18 0
      es/es_test.go
  5. 66 5
      es/esv7.go
  6. 105 39
      esv7/elasticutil.go
  7. 41 0
      esv7/es_test.go
  8. 4 3
      etcd/etcd.go
  9. 4 3
      go-xweb/httpsession/memorystore.go
  10. 9 4
      go-xweb/httpsession/redissessionstore.go
  11. 2 2
      go-xweb/httpsession/session.go
  12. 1 1
      go-xweb/httpsession/store.go
  13. 26 18
      go-xweb/xweb/action.go
  14. 43 2
      go.mod
  15. 22 497
      go.sum
  16. 26 0
      gocaptcha/.gitignore
  17. 201 0
      gocaptcha/LICENSE
  18. 68 0
      gocaptcha/README.md
  19. 89 0
      gocaptcha/blur.go
  20. 39 0
      gocaptcha/blur_test.go
  21. 129 0
      gocaptcha/captcha.go
  22. 24 0
      gocaptcha/captcha_test.go
  23. 101 0
      gocaptcha/font.go
  24. 157 0
      gocaptcha/font_test.go
  25. BIN
      gocaptcha/fonts/3Dumb.ttf
  26. BIN
      gocaptcha/fonts/D3Parallelism.ttf
  27. BIN
      gocaptcha/fonts/Flim-Flam.ttf
  28. BIN
      gocaptcha/fonts/chromohv.ttf
  29. 255 0
      gocaptcha/line.go
  30. 144 0
      gocaptcha/line_test.go
  31. 135 0
      gocaptcha/noise.go
  32. 128 0
      gocaptcha/noise_test.go
  33. 100 0
      gocaptcha/random.go
  34. 24 0
      gocaptcha/rnd.go
  35. 11 0
      gocaptcha/rnd_test.go
  36. 121 0
      gocaptcha/start.go
  37. 28 0
      gocaptcha/start_test.go
  38. 117 0
      gocaptcha/store.go
  39. 79 0
      gocaptcha/store_test.go
  40. BIN
      gocaptcha/testdata/Hiragino Sans GB.ttc
  41. 173 0
      gocaptcha/text.go
  42. 112 0
      gocaptcha/text_test.go
  43. 99 0
      gocaptcha/utils.go
  44. 215 0
      gocaptcha/utils_test.go
  45. 53 0
      gocaptcha/verify.go
  46. 1 0
      ipmatch/README.md
  47. 107 0
      ipmatch/ipmatch.go
  48. 230 0
      log/log.go
  49. 53 0
      log/log_test.go
  50. 34 11
      mail/gmail.go
  51. 4 0
      mongodb/mongodb.go
  52. 94 46
      mysql/mysql.go
  53. 35 0
      mysql/mysql_test.go
  54. BIN
      procimg/main.jpg
  55. BIN
      procimg/new.jpg
  56. 12 0
      procimg/procimg.go
  57. 26 0
      procimg/procimg_test.go
  58. 24 0
      rpc/rpccall.go
  59. 26 0
      rpc/task.go
  60. 10 4
      rpc/weixin.go
  61. 2 1
      sms/sms.go
  62. 3 2
      sms/sms_test.go
  63. 15 3
      sort/sort.go

+ 16 - 3
common/common.go

@@ -293,10 +293,12 @@ func IntAllDef(num interface{}, defaultNum int) int {
 func ObjToString(old interface{}) string {
 	if nil == old {
 		return ""
-	} else {
-		r, _ := old.(string)
-		return r
+	} else if r1, ok1 := old.(string); ok1 {
+		return r1
+	} else if r2, ok2 := old.(*string); ok2 {
+		return *r2
 	}
+	return ""
 }
 
 func ObjToStringDef(old interface{}, defaultstr string) string {
@@ -804,3 +806,14 @@ func ShortenTxt(length int, fixed, shorten string) string {
 	}
 	return ""
 }
+
+//获取问号占位符数量及对应的参数值
+func WhArgs(args []string) (string, []interface{}) {
+	newArgs := make([]interface{}, len(args))
+	wh := make([]string, len(args))
+	for k, v := range args {
+		newArgs[k] = v
+		wh[k] = "?"
+	}
+	return strings.Join(wh, ","), newArgs
+}

+ 48 - 3
common/requtil.go

@@ -2,12 +2,25 @@
 package common
 
 import (
+	"fmt"
 	"net"
 	"net/http"
 	"strings"
 )
 
-//获取平台类型
+// 获取操作系统类型
+func GetSystem(userAgent string) string {
+	userAgent = strings.ToLower(userAgent)
+	if strings.Contains(userAgent, "android") {
+		return "android"
+	} else if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipad") || strings.Contains(userAgent, "ipod") {
+		return "ios"
+	} else {
+		return "pc"
+	}
+}
+
+// 获取平台类型
 func GetOS(useros string) string {
 	osVersion := "其他"
 	if strings.Contains(useros, "NT 6.0") {
@@ -36,7 +49,7 @@ func GetOS(useros string) string {
 	return osVersion
 }
 
-//获取浏览器类型
+// 获取浏览器类型
 func GetBrowse(userbrowser string) string {
 	browserVersion := "其他"
 	if strings.Contains(userbrowser, "MSIE") {
@@ -53,7 +66,7 @@ func GetBrowse(userbrowser string) string {
 	return browserVersion
 }
 
-//获取ip
+// 获取ip
 func GetIp(req *http.Request) string {
 	if req == nil {
 		return ""
@@ -74,3 +87,35 @@ func GetIp(req *http.Request) string {
 	ip, _, _ := net.SplitHostPort(req.RemoteAddr)
 	return ip
 }
+
+// 获取当前服务器ip
+func GetLocationIp() (ips []string) {
+	// 获取所有网络接口
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		fmt.Println("获取网络接口错误:", err)
+		return
+	}
+	// 遍历网络接口,找到非回环接口的 IPv4 地址
+	for _, iface := range interfaces {
+		// 排除回环接口和无效接口
+		if iface.Flags&net.FlagLoopback == 0 && iface.Flags&net.FlagUp != 0 {
+			addresses, err := iface.Addrs()
+			if err != nil {
+				fmt.Println("获取接口地址错误:", err)
+				return
+			}
+
+			// 遍历接口地址,找到第一个 IPv4 地址
+			for _, addr := range addresses {
+				ipNet, ok := addr.(*net.IPNet)
+				if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
+					ip := ipNet.IP
+					ips = append(ips, fmt.Sprint(ip))
+					fmt.Println("当前服务器环境的 IP 地址:", ip)
+				}
+			}
+		}
+	}
+	return
+}

+ 25 - 24
encrypt/encryptarticle.go

@@ -6,64 +6,65 @@ import (
 	"strings"
 )
 
-//正文
+// 正文
 var SE = &SimpleEncrypt{Key: "topnet2015topnet2015"}
 var SE2 = &SimpleEncrypt{Key: "2017jianyu"}
 var SE3 = &SimpleEncrypt{Key: "entservice"}
 
-//百度
+// 百度
 var BSE = &SimpleEncrypt{Key: "HNtopnet2017jy"}
 var BSE2 = &SimpleEncrypt{Key: "TOP2017jianyu"}
 
-//订阅推送邮件
+// 订阅推送邮件
 var ESE = &SimpleEncrypt{Key: "PEjy2018topnet"}
 var ESE2 = &SimpleEncrypt{Key: "TopnetJy2018Pe"}
 
-//首页
+// 首页
 var ISE = &SimpleEncrypt{Key: "Indexjy2021topnet"}
 var ISE2 = &SimpleEncrypt{Key: "TopnetJy2021Pe"}
 
-//通用加密
+// 通用加密
 func CommonEncodeArticle(stype string, keys ...string) (id string) {
 	switch stype {
-	case "content":
+	case "content", "yyszb":
 		id = BEncodeArticleId2ByCheck("A", SE, SE2, keys...)
 	case "bdprivate":
 		id = BEncodeArticleId2ByCheck("B", BSE, BSE2, keys...)
 	case "mailprivate":
 		id = BEncodeArticleId2ByCheck("C", ESE, ESE2, keys...)
 	case "indexcontent":
-		id = BEncodeArticleId2ByCheck("D", ISE, ISE2, keys...)	
+		id = BEncodeArticleId2ByCheck("D", ISE, ISE2, keys...)
 	}
-
 	return
 }
 
-//通用解密
+// 通用解密
 func CommonDecodeArticle(stype string, id string) (res []string) {
-        switch stype {
-        case "content":
-	      res = BDecodeArticleId2ByCheck(id, SE, SE2)
-        case "bdprivate":
-	      res = BDecodeArticleId2ByCheck(id, BSE, BSE2)
-        case "mailprivate":
-	      res = BDecodeArticleId2ByCheck(id, ESE, ESE2)
-        case "indexcontent":
-	      res = BDecodeArticleId2ByCheck(id, ISE, ISE2)
-        case "advancedProject":
-	      res = BDecodeArticleId2ByCheck(id, SE, SE2)
-        }
-        return
+	switch stype {
+	case "content", "bdcontent", "yyszb":
+		res = BDecodeArticleId2ByCheck(id, SE, SE2)
+	case "bdprivate":
+		res = BDecodeArticleId2ByCheck(id, BSE, BSE2)
+	case "mailprivate":
+		res = BDecodeArticleId2ByCheck(id, ESE, ESE2)
+	case "indexcontent":
+		res = BDecodeArticleId2ByCheck(id, ISE, ISE2)
+	case "advancedProject":
+		res = BDecodeArticleId2ByCheck(id, SE, SE2)
+	case "entservice":
+		res = []string{SE3.DecodeString(id)}
+	}
+	return
 }
 
-//短地址加密,二次加密带校验和
+// 短地址加密,二次加密带校验和
 func BEncodeArticleId2ByCheck(h string, s1, s2 *SimpleEncrypt, keys ...string) string {
 	kstr := strings.Join(keys, ",")
 	kstr = s1.EncodeStringByCheck(kstr)
 	return url.QueryEscape(h + common.GetLetterRandom(2) + s2.EncodeStringByCheck(kstr))
 }
 
-//短地址解密,二次解密带校验和
+// 短地址解密,二次解密带校验和
 func BDecodeArticleId2ByCheck(id string, s1, s2 *SimpleEncrypt) []string {
 	if !strings.Contains(id, "+") { //新加密算法解密
 		id, _ = url.QueryUnescape(id)

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 18 - 0
es/es_test.go


+ 66 - 5
es/esv7.go

@@ -3,7 +3,9 @@ package es
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"io"
 	"log"
 	"net/http"
 	"runtime"
@@ -15,6 +17,19 @@ import (
 	es "github.com/olivere/elastic/v7"
 )
 
+type MySource struct {
+	Query string
+}
+
+func (m *MySource) Source() (interface{}, error) {
+	mp := make(map[string]interface{})
+	err := json.Unmarshal([]byte(m.Query), &mp)
+	if err != nil {
+		return nil, err
+	}
+	return mp, nil
+}
+
 type EsV7 struct {
 	Address      string
 	UserName     string
@@ -179,7 +194,9 @@ func (e *EsV7) get(index, itype, searchType, query string, isLimit, isHighlight
 		total = searchResult.TotalHits()
 		if searchResult.Hits != nil {
 			resNum = len(searchResult.Hits.Hits)
-			if isLimit && resNum <= 5000 {
+			if isLimit && resNum > 5000 {
+				log.Println("查询结果太多,查询到:", resNum, "条")
+			} else {
 				res = make([]map[string]interface{}, resNum)
 				for i, hit := range searchResult.Hits.Hits {
 					//d := json.NewDecoder(bytes.NewBuffer(*hit.Source))
@@ -193,10 +210,7 @@ func (e *EsV7) get(index, itype, searchType, query string, isLimit, isHighlight
 						res[i]["highlight"] = map[string][]string(hit.Highlight)
 					}
 				}
-			} else {
-				log.Println("查询结果太多,查询到:", resNum, "条")
 			}
-
 		}
 	}
 	return total, resNum, &res
@@ -900,13 +914,16 @@ func (e *EsV7) GetWithCount(index, itype, countQuery, allQuery string) (int64, *
 	client := e.GetEsConn()
 	defer e.DestoryEsConn(client)
 	var res []map[string]interface{}
-	count := e.Count(index, itype, countQuery)
+	var count int64
 	if client != nil {
+		allQuery = strings.TrimPrefix(strings.TrimSpace(allQuery), "{")
+		allQuery = `{"track_total_hits": true,` + allQuery
 		searchResult, err := client.Search().Index(index).Source(allQuery).Do(context.TODO())
 		if err != nil {
 			log.Println("从ES查询出错", err.Error())
 			return count, nil
 		}
+		count = searchResult.TotalHits()
 		if searchResult.Hits != nil {
 			resNum := len(searchResult.Hits.Hits)
 			if resNum <= 5000 {
@@ -987,3 +1004,47 @@ func (e *EsV7) analyzeResp(text, analyzer, u string) (*http.Request, error) {
 	req.Header.Set("Content-Type", "application/json")
 	return req, err
 }
+
+func (e *EsV7) Scroll(indexName, scrollTime, query string, f func(fv map[string]interface{}) bool) {
+	client := e.GetEsConn()
+	defer e.DestoryEsConn(client)
+	// 开始滚动查询
+	scrollService := client.Scroll(indexName).Scroll(scrollTime).Body(query)
+	// 滚动 ID 用于追踪滚动上下文
+	var scrollID string
+	for {
+		// 执行滚动查询
+		searchResult, err := scrollService.Do(context.Background())
+		if errors.Is(err, io.EOF) {
+			break
+		} else if err != nil {
+			log.Println("Error executing scroll query: ", err)
+			break
+		}
+		// 获取滚动 ID
+		scrollID = searchResult.ScrollId
+		// 处理查询结果
+		isBreak := false
+		for _, hit := range searchResult.Hits.Hits {
+			// 这里假设文档的源数据是 JSON 字符串
+			var source map[string]interface{}
+			json.Unmarshal(hit.Source, &source)
+			if !f(source) {
+				isBreak = true
+				break
+			}
+		}
+		if isBreak {
+			break
+		}
+		// 更新滚动服务以使用新的滚动 ID
+		scrollService = client.Scroll(indexName).ScrollId(scrollID).Scroll(scrollTime)
+	}
+	if scrollID != "" {
+		// 清理滚动上下文
+		_, err := client.ClearScroll().ScrollId(scrollID).Do(context.Background())
+		if err != nil {
+			log.Println(scrollID, "Error clearing scroll: ", err)
+		}
+	}
+}

+ 105 - 39
esv7/elasticutil.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"log"
 	"reflect"
+	"regexp"
 	"runtime"
 	"strconv"
 	"strings"
@@ -15,7 +16,7 @@ import (
 	es "github.com/olivere/elastic/v7"
 )
 
-//检索库服务地址
+// 检索库服务地址
 var (
 	addrs    []string
 	LocCity  = map[string]string{}
@@ -33,8 +34,9 @@ var pool chan *es.Client
 var ntimeout int
 
 var syncPool sync.Pool
+var filterReg = regexp.MustCompile(`,\s*"should"\s*:\s*\[\s*\]\s*,\s*"minimum_should_match"\s*:\s*1`)
 
-//初始化全文检索
+// 初始化全文检索
 func InitElastic(addr string) {
 	InitElasticSize(addr, SIZE)
 }
@@ -59,12 +61,12 @@ var httpclient = &http.Client{Transport: &http.Transport{
 //var op = es.SetHttpClient(httpclient)
 var poolsize = int32(20)
 
-//n倍的池
+// n倍的池
 func InitElasticSize(addr string, size int) {
 	InitElasticSizeByAuth(addr, size, "", "")
 }
 
-//初始化es,带有用户名密码认证
+// 初始化es,带有用户名密码认证
 func InitElasticSizeByAuth(addr string, size int, u, p string) {
 	poolsize = int32(3 * size)
 	pool = make(chan *es.Client, poolsize)
@@ -86,7 +88,7 @@ func newClient() (*es.Client, error) {
 	return es.NewClient(opt...)
 }
 
-//关闭连接
+// 关闭连接
 func DestoryEsConn(client *es.Client) {
 	select {
 	case pool <- client:
@@ -104,7 +106,7 @@ var (
 	lastTimeLock = &sync.Mutex{}
 )
 
-//获取连接
+// 获取连接
 func GetEsConn() *es.Client {
 	select {
 	case c := <-pool:
@@ -139,7 +141,7 @@ func GetEsConn() *es.Client {
 	}
 }
 
-//保存对象
+// 保存对象
 func Save(index, itype string, obj interface{}) bool {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -166,23 +168,48 @@ func Save(index, itype string, obj interface{}) bool {
 	} else {
 		return true
 	}
+}
 
+func SaveNew(index string, obj interface{}) bool {
+	client := GetEsConn()
+	defer DestoryEsConn(client)
+	defer func() {
+		if r := recover(); r != nil {
+			log.Println("[E]", r)
+			for skip := 1; ; skip++ {
+				_, file, line, ok := runtime.Caller(skip)
+				if !ok {
+					break
+				}
+				go log.Printf("%v,%v\n", file, line)
+			}
+		}
+	}()
+	data := objToMap(obj)
+	_id := fmt.Sprint((*data)["id"])
+	_, err := client.Index().Index(index).Id(_id).BodyJson(data).Do(context.TODO())
+	if err != nil {
+		log.Println("保存到ES出错", err.Error(), obj)
+		return false
+	} else {
+		return true
+	}
 }
 
-//通用查询
-//{"query": {"bool":{"must":[{"query_string":{"default_field":"name","query":"*"}}]}}}
-//{"query":{"bool":{"must":{"match":{"content":{"query":"fulltextsearch","operator":"and"}}},"should":[{"match":{"content":{"query":"Elasticsearch","boost":3}}},{"match":{"content":{"query":"Lucene","boost":2}}}]}}}
-//prefix
-//{"query":{"match":{"title":{"query":"brownfox","operator":"and"}}}} //默认为or
-//{"query":{"multi_match":{"query":"PolandStreetW1V","type":"most_fields","fields":["*_street","city^2","country","postcode"]}}}
-//{"query":{"wildcard":{"postcode":"W?F*HW"}}}
-//{"query":{"regexp":{"postcode":"W[0-9].+"}}}
-//{"query":{"filtered":{"filter":{"range":{"price":{"gte":10000}}}}},"aggs":{"single_avg_price":{"avg":{"field":"price"}}}}
-//{"query":{"match":{"make":"ford"}},"aggs":{"colors":{"terms":{"field":"color"}}}}//查fork有几种颜色
-//过滤器不会计算相关度的得分,所以它们在计算上更快一些
-//{"query":{"filtered":{"query":{"match_all":{}},"filter":{"range":{"balance":{"gte":20000,"lte":30000}}}}}}
-//{"query":{"match_all":{}},"from":10,"size":10,"_source":["account_number","balance"],"sort":{"balance":{"order":"desc"}}}
-//{"query":{"match_phrase":{"address":"milllane"}}}和match不同会去匹配整个短语,相当于must[]
+// 通用查询
+// {"query": {"bool":{"must":[{"query_string":{"default_field":"name","query":"*"}}]}}}
+// {"query":{"bool":{"must":{"match":{"content":{"query":"fulltextsearch","operator":"and"}}},"should":[{"match":{"content":{"query":"Elasticsearch","boost":3}}},{"match":{"content":{"query":"Lucene","boost":2}}}]}}}
+// prefix
+// {"query":{"match":{"title":{"query":"brownfox","operator":"and"}}}} //默认为or
+// {"query":{"multi_match":{"query":"PolandStreetW1V","type":"most_fields","fields":["*_street","city^2","country","postcode"]}}}
+// {"query":{"wildcard":{"postcode":"W?F*HW"}}}
+// {"query":{"regexp":{"postcode":"W[0-9].+"}}}
+// {"query":{"filtered":{"filter":{"range":{"price":{"gte":10000}}}}},"aggs":{"single_avg_price":{"avg":{"field":"price"}}}}
+// {"query":{"match":{"make":"ford"}},"aggs":{"colors":{"terms":{"field":"color"}}}}//查fork有几种颜色
+// 过滤器不会计算相关度的得分,所以它们在计算上更快一些
+// {"query":{"filtered":{"query":{"match_all":{}},"filter":{"range":{"balance":{"gte":20000,"lte":30000}}}}}}
+// {"query":{"match_all":{}},"from":10,"size":10,"_source":["account_number","balance"],"sort":{"balance":{"order":"desc"}}}
+// {"query":{"match_phrase":{"address":"milllane"}}}和match不同会去匹配整个短语,相当于must[]
 func GetBySearchType(index, searchType, query string) (int64, *[]map[string]interface{}) {
 	t, _, l := get(index, "", searchType, query, true, true)
 	return t, l
@@ -192,6 +219,7 @@ func Get(index, itype, query string) *[]map[string]interface{} {
 	return r
 }
 func get(index, itype, searchType, query string, isLimit, isHighlight bool) (int64, int, *[]map[string]interface{}) {
+	query = filterReg.ReplaceAllString(query, "")
 	//log.Println("query  -- ", query)
 	client := GetEsConn()
 	defer func() {
@@ -257,14 +285,14 @@ func GetNoLimit(index, itype, query string) *[]map[string]interface{} {
 	return l
 }
 
-//分页查询
-//{"name":"张三","$and":[{"age":{"$gt":10}},{"age":{"$lte":20}}]}
-//fields直接是 `"_id","title"`
+// 分页查询
+// {"name":"张三","$and":[{"age":{"$gt":10}},{"age":{"$lte":20}}]}
+// fields直接是 `"_id","title"`
 func GetPage(index, itype, query, order, field string, start, limit int) *[]map[string]interface{} {
 	return Get(index, itype, MakeQuery(query, order, field, start, limit))
 }
 
-//openapi
+// openapi
 func GetOAPage(index, itype, query, order, field string, start, limit int) (*[]map[string]interface{}, int) {
 	return GetOA(index, itype, MakeQuery(query, order, field, start, limit))
 }
@@ -291,7 +319,7 @@ func MakeQuery(query, order, fileds string, start, limit int) string {
 	return ""
 }
 
-//{"name":"aaa"}
+// {"name":"aaa"}
 func AnalyQuery(query interface{}, parent string, result string) string {
 	m := make(map[string]interface{})
 	if q1, ok := query.(string); ok {
@@ -413,7 +441,7 @@ func GetByIdField(index, itype, id, fields string) *map[string]interface{} {
 	return nil
 }
 
-//根据id来查询文档
+// 根据id来查询文档
 func GetById(index, itype string, ids ...string) *[]map[string]interface{} {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -457,7 +485,7 @@ func GetById(index, itype string, ids ...string) *[]map[string]interface{} {
 	return &res
 }
 
-//根据语句更新对象
+// 根据语句更新对象
 func Update(index, itype, id string, updateStr string) bool {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -542,7 +570,7 @@ func NewBulkUpdate(index string, params ...[]string) bool {
 	return false
 }
 
-//根据id删除索引对象
+// 根据id删除索引对象
 func DelById(index, itype, id string) bool {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -571,7 +599,7 @@ func DelById(index, itype, id string) bool {
 	return b
 }
 
-//先删除后增
+// 先删除后增
 func UpdateNewDoc(index, itype string, obj ...interface{}) bool {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -613,6 +641,45 @@ func UpdateNewDoc(index, itype string, obj ...interface{}) bool {
 	return b
 }
 
+func UpdateNew(index string, obj ...interface{}) bool {
+	client := GetEsConn()
+	defer DestoryEsConn(client)
+	b := false
+	if client != nil {
+		defer func() {
+			if r := recover(); r != nil {
+				log.Println("[E]", r)
+				for skip := 1; ; skip++ {
+					_, file, line, ok := runtime.Caller(skip)
+					if !ok {
+						break
+					}
+					go log.Printf("%v,%v\n", file, line)
+				}
+			}
+		}()
+		var err error
+		for _, v := range obj {
+			tempObj := objToMap(v)
+			if tempObj == nil || len(*tempObj) == 0 {
+				continue
+			}
+			_id := fmt.Sprint((*tempObj)["id"])
+			if _id != "" {
+				client.Delete().Index(index).Id(_id).Do(context.TODO())
+			}
+			_, err = client.Index().Index(index).Id(_id).BodyJson(tempObj).Do(context.TODO())
+			if err != nil {
+				log.Println("保存到ES出错", err.Error())
+			} else {
+				b = true
+			}
+		}
+
+	}
+	return b
+}
+
 func BulkSave(index, itype string, obj *[]map[string]interface{}, isDelBefore bool) {
 	client := GetEsConn()
 	defer DestoryEsConn(client)
@@ -798,7 +865,7 @@ const (
 	HL_IK           = `"highlight": {"pre_tags": ["` + IK_pre_tags + `"],"post_tags": ["` + IK_post_tags + `"],"fields": {%s}}`
 )
 
-//替换了"号
+// 替换了"号
 func GetNgramQuery(query interface{}, mustquery, findfields string) (qstr string) {
 	var words []string
 	if q, ok := query.(string); ok {
@@ -891,7 +958,7 @@ func GetByNgram(index, itype string, query interface{}, mustquery, findfields, o
 	return GetByNgramAll(index, itype, query, mustquery, findfields, order, fields, start, limit, false, false)
 }
 
-//增加高亮、过滤查询、高亮截取字数
+// 增加高亮、过滤查询、高亮截取字数
 func GetByNgramOther(index, itype string, query interface{}, mustquery, findfields, order, fields string, start, limit int, highlight bool, filtermode bool, count int) *[]map[string]interface{} {
 	defer catch()
 	qstr := ""
@@ -925,8 +992,8 @@ func GetByNgramOther(index, itype string, query interface{}, mustquery, findfiel
 	}
 }
 
-//增加高亮、过滤查询
-//替换了"号
+// 增加高亮、过滤查询
+// 替换了"号
 func GetByNgramAll(index, itype string, query interface{}, mustquery, findfields, order, fields string, start, limit int, highlight bool, filtermode bool) *[]map[string]interface{} {
 	defer catch()
 	qstr := ""
@@ -962,7 +1029,7 @@ func GetByNgramAll(index, itype string, query interface{}, mustquery, findfields
 	}
 }
 
-//增加高亮、过滤查询
+// 增加高亮、过滤查询
 func GetByNgramAll_New(index, itype string, querystring, querymust interface{}, mustquery, findfields, order, fields string, start, limit int, highlight bool, filtermode bool) *[]map[string]interface{} {
 	defer catch()
 	qstr := ""
@@ -1002,7 +1069,7 @@ type KeyConfig struct {
 	Areas     []string `json:"area"`
 }
 
-//替换了"号
+// 替换了"号
 func GetResForJY(index, itype string, keys []KeyConfig, allquery, findfields, SortQuery, fields string, start, limit int) *[]map[string]interface{} {
 	if len(keys) > 0 {
 		qstr := ""
@@ -1060,7 +1127,6 @@ func ReplaceYH(src string) (rpl string) {
 	return strings.Replace(src, `"`, `\"`, -1)
 }
 
-//
 func GetAllByNgram(index, itype, qstr, findfields, order, fields string, start, limit, count int, highlight bool) *[]map[string]interface{} {
 	if qstr != "" {
 		if highlight {
@@ -1086,7 +1152,7 @@ func GetAllByNgram(index, itype, qstr, findfields, order, fields string, start,
 	}
 }
 
-//数据标记2019-07-10
+// 数据标记2019-07-10
 func GetAllByNgram_MP(index, itype, qstr, findfields, order, fields string, start, limit, count int, highlight bool) *[]map[string]interface{} {
 	if qstr != "" {
 		if highlight {
@@ -1112,7 +1178,7 @@ func GetAllByNgram_MP(index, itype, qstr, findfields, order, fields string, star
 	}
 }
 
-//ik 分词
+// ik 分词
 func GetAllByIk(index, itype, qstr, findfields, order, fields string, start, limit, count int, highlight bool) *[]map[string]interface{} {
 	if qstr != "" {
 		if highlight {

+ 41 - 0
esv7/es_test.go

@@ -0,0 +1,41 @@
+package elastic
+
+import (
+	"log"
+	"testing"
+)
+
+type AggregationsBucket struct {
+	Key       string `json:"key"`
+	Doc_count int    `json:"doc_count"`
+	Count     struct {
+		Value float64 `json:value`
+	} `json:"count"`
+}
+
+func TestGet(t *testing.T) {
+	q := `{
+	  "query": {
+	    "bool": {
+	      "must": [
+	        {
+	          "term": {
+	            "id": "64e6b70abc72bfca100f4b63"
+	          }
+	        }
+	      ]
+	    }
+	  }
+	}`
+	NewEs("v7", "http://127.0.0.1:19805", 2, "elastic", "MsOCrY7Yct3sjVvB5gCd")
+	//NewEs("v1", "http://192.168.3.206:9800", 2, "", "")
+	list := Get("bidding", "bidding", q)
+	log.Println(list)
+}
+
+func TestAnalyze(t *testing.T) {
+	NewEs("v7", "http://192.168.3.241:9205,http://192.168.3.149:9200", 2, "", "")
+	//NewEs("v1", "http://192.168.3.206:9800", 2, "", "")
+	res := Analyze("软件中国", "bidding", "ik_smart")
+	log.Println(res)
+}

+ 4 - 3
etcd/etcd.go

@@ -10,8 +10,8 @@ import (
 	"syscall"
 	"time"
 
+	"app.yhyue.com/moapp/jybase/common"
 	"app.yhyue.com/moapp/jybase/iputil"
-	"github.com/zeromicro/go-zero/core/threading"
 	clientv3 "go.etcd.io/etcd/client/v3"
 )
 
@@ -155,7 +155,8 @@ func (p *Publisher) keepAliveAsync(cli *clientv3.Client) error {
 	if err != nil {
 		return err
 	}
-	threading.GoSafe(func() {
+	go func() {
+		defer common.Catch()
 		for {
 			select {
 			case <-p.stop:
@@ -173,7 +174,7 @@ func (p *Publisher) keepAliveAsync(cli *clientv3.Client) error {
 				}
 			}
 		}
-	})
+	}()
 	return nil
 }
 

+ 4 - 3
go-xweb/httpsession/memorystore.go

@@ -44,13 +44,14 @@ func (node *sessionNode) Set(key string, v interface{}) {
 	node.lock.Unlock()
 }
 
-func (node *sessionNode) SetMultiple(m map[string]interface{}) {
+func (node *sessionNode) SetMultiple(m map[string]interface{}) error {
 	node.lock.Lock()
 	for k, v := range m {
 		node.kvs[k] = v
 	}
 	node.last = time.Now()
 	node.lock.Unlock()
+	return nil
 }
 
 func (node *sessionNode) Del(keys ...string) {
@@ -162,7 +163,7 @@ func (store *MemoryStore) Set(id Id, key string, value interface{}) {
 	node.Set(key, value)
 }
 
-func (store *MemoryStore) SetMultiple(id Id, m map[string]interface{}) {
+func (store *MemoryStore) SetMultiple(id Id, m map[string]interface{}) error {
 	store.lock.RLock()
 	node, ok := store.nodes[id]
 	store.lock.RUnlock()
@@ -175,7 +176,7 @@ func (store *MemoryStore) SetMultiple(id Id, m map[string]interface{}) {
 		store.nodes[id] = node
 		store.lock.Unlock()
 	}
-	node.SetMultiple(m)
+	return node.SetMultiple(m)
 }
 
 func (store *MemoryStore) newNode() *sessionNode {

+ 9 - 4
go-xweb/httpsession/redissessionstore.go

@@ -61,7 +61,7 @@ func (store *redisStore) Set(id Id, key string, value interface{}) {
 }
 
 //同时设置多个值
-func (store *redisStore) SetMultiple(id Id, m map[string]interface{}) {
+func (store *redisStore) SetMultiple(id Id, m map[string]interface{}) error {
 	lock(id).Lock()
 	defer lock(id).Unlock()
 	var userdata map[string]interface{}
@@ -69,7 +69,9 @@ func (store *redisStore) SetMultiple(id Id, m map[string]interface{}) {
 	if err != nil {
 		userdata = make(map[string]interface{})
 	} else {
-		json.Unmarshal(*bs, &userdata)
+		if err = json.Unmarshal(*bs, &userdata); err != nil {
+			return err
+		}
 	}
 	for k, v := range m {
 		userdata[k] = v
@@ -78,8 +80,11 @@ func (store *redisStore) SetMultiple(id Id, m map[string]interface{}) {
 	if RedisNotLoginKey != "" && userdata[RedisNotLoginKey] == nil {
 		timeout = RedisNotLoginExpire
 	}
-	putdata, _ := json.Marshal(userdata)
-	redis.PutBytes("session", string(id), &putdata, timeout)
+	putdata, err := json.Marshal(userdata)
+	if err != nil {
+		return err
+	}
+	return redis.PutBytes("session", string(id), &putdata, timeout)
 }
 
 func (store *redisStore) Add(id Id) {

+ 2 - 2
go-xweb/httpsession/session.go

@@ -31,8 +31,8 @@ func (session *Session) Set(key string, value interface{}) {
 	session.manager.store.Set(session.id, key, value)
 }
 
-func (session *Session) SetMultiple(m map[string]interface{}) {
-	session.manager.store.SetMultiple(session.id, m)
+func (session *Session) SetMultiple(m map[string]interface{}) error {
+	return session.manager.store.SetMultiple(session.id, m)
 }
 
 func (session *Session) Del(keys ...string) bool {

+ 1 - 1
go-xweb/httpsession/store.go

@@ -8,7 +8,7 @@ type Store interface {
 	Get(id Id, key string) interface{}
 	GetMultiple(id Id) map[string]interface{}
 	Set(id Id, key string, value interface{})
-	SetMultiple(id Id, m map[string]interface{})
+	SetMultiple(id Id, m map[string]interface{}) error
 	Del(id Id, keys ...string) bool
 	Clear(id Id) bool
 	Add(id Id)

+ 26 - 18
go-xweb/xweb/action.go

@@ -56,6 +56,7 @@ type Action struct {
 	RootTemplate *template.Template
 	RequestBody  []byte
 	StatusCode   int
+	SessionMap   map[string]interface{}
 }
 
 type Mapper struct {
@@ -67,7 +68,7 @@ func XsrfName() string {
 	return XSRF_TAG
 }
 
-//[SWH|+]:
+// [SWH|+]:
 // Protocol returns request protocol name, such as HTTP/1.1 .
 func (c *Action) Protocol() string {
 	return c.Request.Proto
@@ -378,21 +379,21 @@ func (c *Action) NotFound(message string) error {
 
 // ParseStruct mapping forms' name and values to struct's field
 // For example:
-//		<form>
-//			<input name="user.id"/>
-//			<input name="user.name"/>
-//			<input name="user.age"/>
-//		</form>
 //
-//		type User struct {
-//			Id int64
-//			Name string
-//			Age string
-//		}
+//	<form>
+//		<input name="user.id"/>
+//		<input name="user.name"/>
+//		<input name="user.age"/>
+//	</form>
 //
-//		var user User
-//		err := action.MapForm(&user)
+//	type User struct {
+//		Id int64
+//		Name string
+//		Age string
+//	}
 //
+//	var user User
+//	err := action.MapForm(&user)
 func (c *Action) MapForm(st interface{}, names ...string) error {
 	v := reflect.ValueOf(st)
 	var name string
@@ -708,7 +709,7 @@ func (c *Action) Render(tmpl string, params ...*T) error {
 	return err
 }
 
-//仅生成网页内容
+// 仅生成网页内容
 var regInclude = regexp.MustCompile(`\{\{\s*include\s*"(.*?\.html)".*\}\}`)
 
 func (c *Action) NamedRender4Cache(name, content string, params ...*T) ([]byte, error) {
@@ -754,7 +755,7 @@ func (c *Action) NamedRender4Cache(name, content string, params ...*T) ([]byte,
 	return nil, err
 }
 
-//生成可缓存的数据但并未写到流中
+// 生成可缓存的数据但并未写到流中
 func (c *Action) Render4Cache(tmpl string, params ...*T) ([]byte, error) {
 	content, err := c.getTemplate(tmpl)
 
@@ -858,7 +859,7 @@ func (c *Action) GetForm() url.Values {
 	return c.Request.Form
 }
 
-//增加_过滤脚本等
+// 增加_过滤脚本等
 func FilterXSS(str string) string {
 	str = strings.Replace(str, "<", "&#60;", -1)
 	str = strings.Replace(str, ">", "&#62;", -1)
@@ -869,7 +870,7 @@ func FilterXSS(str string) string {
 	return str
 }
 
-//增加_原GetString方法
+// 增加_原GetString方法
 func (c *Action) GetStringComm(key string) string {
 	s := c.GetSlice(key)
 	if len(s) > 0 {
@@ -878,7 +879,7 @@ func (c *Action) GetStringComm(key string) string {
 	return ""
 }
 
-//修改_防Xss注入
+// 修改_防Xss注入
 func (c *Action) GetString(key string) string {
 	return FilterXSS(c.GetStringComm(key))
 }
@@ -950,14 +951,21 @@ func (c *Action) Session() *httpsession.Session {
 }
 
 func (c *Action) GetSession(key string) interface{} {
+	//if c.SessionMap == nil {
+	//	c.SessionMap = c.session.GetMultiple()
+	//}
 	return c.Session().Get(key)
 }
 
 func (c *Action) SetSession(key string, value interface{}) {
+	//if c.SessionMap != nil {
+	//	c.SessionMap[key] = value
+	//}
 	c.Session().Set(key, value)
 }
 
 func (c *Action) DelSession(key string) {
+	//c.SessionMap = nil
 	c.Session().Del(key)
 }
 

+ 43 - 2
go.mod

@@ -1,23 +1,64 @@
 module app.yhyue.com/moapp/jybase
 
-go 1.13
+go 1.20
 
 require (
 	app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d
+	github.com/RoaringBitmap/roaring v1.5.0
 	github.com/coscms/tagfast v0.0.0-20150925144250-2b69b2496250
 	github.com/donnie4w/go-logger v0.0.0-20170827050443-4740c51383f4
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/garyburd/redigo v1.6.2
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/golang-jwt/jwt/v4 v4.4.2
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
 	github.com/gomodule/redigo v1.8.9
 	github.com/howeyc/fsnotify v0.9.0
 	github.com/olivere/elastic/v7 v7.0.22
-	github.com/zeromicro/go-zero v1.3.5
+	github.com/yl2chen/cidranger v1.0.2
 	go.etcd.io/etcd/client/v3 v3.5.4
 	go.mongodb.org/mongo-driver v1.9.1
+	go.uber.org/zap v1.21.0
+	golang.org/x/image v0.24.0
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 	gorm.io/driver/mysql v1.0.5
 	gorm.io/gorm v1.21.3
 )
+
+require (
+	github.com/bits-and-blooms/bitset v1.2.0 // indirect
+	github.com/coreos/go-semver v0.3.0 // indirect
+	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+	github.com/go-stack/stack v1.8.0 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.1 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/klauspost/compress v1.13.6 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mschoch/smat v0.2.0 // indirect
+	github.com/olivere/elastic v6.2.37+incompatible // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/stretchr/testify v1.8.0 // indirect
+	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+	github.com/xdg-go/scram v1.0.2 // indirect
+	github.com/xdg-go/stringprep v1.0.2 // indirect
+	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
+	go.etcd.io/etcd/api/v3 v3.5.4 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	go.uber.org/goleak v1.1.12 // indirect
+	go.uber.org/multierr v1.8.0 // indirect
+	golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect
+	golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
+	golang.org/x/sync v0.11.0 // indirect
+	golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
+	golang.org/x/text v0.22.0 // indirect
+	google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect
+	google.golang.org/grpc v1.47.0 // indirect
+	google.golang.org/protobuf v1.28.0 // indirect
+)

+ 22 - 497
go.sum

@@ -2,83 +2,29 @@ app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d h1:WPsYuuptAd3UEgN+j
 app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d/go.mod h1:91/lSD/hS+ckMVP3WdidRzDhC60lLMdyce9QHy0cSMA=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
-github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
-github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
-github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
-github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/RoaringBitmap/roaring v1.5.0 h1:V0VCSiHjroItEYCM3guC8T83ehi5QMt3oM9EefTTOms=
+github.com/RoaringBitmap/roaring v1.5.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
-github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
+github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
+github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@@ -88,99 +34,52 @@ github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzA
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coscms/tagfast v0.0.0-20150925144250-2b69b2496250 h1:svuVnTdfOrAZFzcdpau9u91veXKEdpHDNpMzb132vPs=
 github.com/coscms/tagfast v0.0.0-20150925144250-2b69b2496250/go.mod h1:zX8vynptAghuV/KG8BOZlDeo4DsTKWfBQ154RWlkay0=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/donnie4w/go-logger v0.0.0-20170827050443-4740c51383f4 h1:T9PR91sjTtrA1HmZB4G+M7OLCelch0f6rIEY7Mm1T4U=
 github.com/donnie4w/go-logger v0.0.0-20170827050443-4740c51383f4/go.mod h1:L7S4x0R7vv3xoOhGuyAJyCO2MYzWOpccM4Isn8jIUgY=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
 github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
-github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
 github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -197,88 +96,37 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
 github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
-github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
-github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
-github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
-github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
-github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY=
 github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
 github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
-github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
@@ -287,89 +135,36 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
-github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U=
 github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
 github.com/olivere/elastic/v7 v7.0.22 h1:esBA6JJwvYgfms0EVlH7Z+9J4oQ/WUADF2y/nCNDw7s=
 github.com/olivere/elastic/v7 v7.0.22/go.mod h1:VDexNy9NjmtAkrjNoI7tImv7FR4tf5zUA3ickqu5Pc8=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
-github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
-github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
-github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
-github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
-github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
-github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -377,36 +172,17 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
-github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
-github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
-github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
-github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
-github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -417,32 +193,23 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
-github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
+github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
-github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-github.com/zeromicro/go-zero v1.3.5 h1:+3T4Rx/5o/EgLuCE3Qo4X0i+3GCHRYEgkabmfKhhQ7Q=
-github.com/zeromicro/go-zero v1.3.5/go.mod h1:wh4o794b7Ul3W0k35Pw9nc3iB4O0OpaQTMQz/PJc1bc=
 go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
 go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
@@ -451,28 +218,11 @@ go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
 go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
-go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
-go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
-go.opentelemetry.io/otel/exporters/jaeger v1.8.0/go.mod h1:GbWg+ng88rDtx+id26C34QLqw2erqJeAjsCx9AFeHfE=
-go.opentelemetry.io/otel/exporters/zipkin v1.8.0/go.mod h1:0uYAyCuGT67MFV9Z/Mmx93wGuugHw0FbxMc74fs3LNo=
-go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk=
-go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c=
-go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
-go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
-go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
-go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
@@ -484,312 +234,117 @@ go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
 golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
-golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
+golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
-golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM=
 google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
 google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
 google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
@@ -801,7 +356,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -814,17 +368,10 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
-gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -835,7 +382,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -844,26 +390,5 @@ gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohL
 gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME=
 gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.22.9/go.mod h1:rcjO/FPOuvc3x7nQWx29UcDrFJMx82RxDob71ntNH4A=
-k8s.io/apimachinery v0.22.9/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
-k8s.io/client-go v0.22.9/go.mod h1:IoH7exYnoH/zgvHOuVxh2c4yJepcCBt72FzCTisOc4k=
-k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
-k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
-k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
-k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20220706174534-f6158b442e7c h1:hFZO68mv/0xe8+V0gRT9BAq3/31cKjjeVv4nScriuBk=
-k8s.io/utils v0.0.0-20220706174534-f6158b442e7c/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

+ 26 - 0
gocaptcha/.gitignore

@@ -0,0 +1,26 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+.idea
+.git
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof

+ 201 - 0
gocaptcha/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 68 - 0
gocaptcha/README.md

@@ -0,0 +1,68 @@
+# gocaptcha
+一个简单的Go语言实现的验证码
+
+### 图片实例
+
+![image](https://raw.githubusercontent.com/lifei6671/gocaptcha/master/example/image_1.jpg)
+![image](https://raw.githubusercontent.com/lifei6671/gocaptcha/master/example/image_2.jpg)
+![image](https://raw.githubusercontent.com/lifei6671/gocaptcha/master/example/image_3.jpg)
+![image](https://raw.githubusercontent.com/lifei6671/gocaptcha/master/example/image_4.jpg)
+
+## 简介
+
+基于Golang实现的图片验证码生成库,可以实现随机字母个数,随机直线,随机噪点等。可以设置任意多字体,每个验证码随机选一种字体展示。
+
+## 实例
+
+#### 使用:
+
+```
+	go get github.com/lifei6671/gocaptcha
+```
+
+#### 使用的类库
+
+```
+	go get github.com/golang/freetype
+	go get github.com/golang/freetype/truetype
+	go get golang.org/x/image
+```
+
+#### 代码
+具体实例可以查看example目录,有生成的验证码图片。
+
+```
+	
+func Get(w http.ResponseWriter, r *http.Request) {
+	captchaImage := gocaptcha.New(dx, dy, gocaptcha.RandLightColor())
+	err := captchaImage.
+		DrawBorder(gocaptcha.RandDeepColor()).
+		DrawNoise(gocaptcha.NoiseDensityHigh, gocaptcha.NewTextNoiseDrawer(gocaptcha.DefaultDPI)).
+		DrawNoise(gocaptcha.NoiseDensityLower, gocaptcha.NewPointNoiseDrawer()).
+		DrawLine(gocaptcha.NewBezier3DLine(), gocaptcha.RandDeepColor()).
+		DrawText(gocaptcha.NewTwistTextDrawer(gocaptcha.DefaultDPI, gocaptcha.DefaultAmplitude, gocaptcha.DefaultFrequency), gocaptcha.RandText(4)).
+		DrawLine(gocaptcha.NewBeeline(), gocaptcha.RandDeepColor()).
+		//DrawLine(gocaptcha.NewHollowLine(), gocaptcha.RandLightColor()).
+		DrawBlur(gocaptcha.NewGaussianBlur(), gocaptcha.DefaultBlurKernelSize, gocaptcha.DefaultBlurSigma).
+		Error
+	
+	if err != nil {
+		fmt.Println(err)
+	}
+	
+	_ = captchaImage.Encode(w, gocaptcha.ImageFormatJpeg)
+}
+
+// 初始化字体
+func init() {
+	err := gocaptcha.SetFontPath("../fonts/")
+	if err != nil {
+		panic(err)
+	}
+}
+
+```
+
+
+
+

+ 89 - 0
gocaptcha/blur.go

@@ -0,0 +1,89 @@
+package gocaptcha
+
+import (
+	"image/color"
+	"image/draw"
+	"math"
+)
+
+type BlurDrawer interface {
+	DrawBlur(canvas draw.Image, kernelSize int, sigma float64) error
+}
+
+type gaussianBlur struct {
+}
+
+func NewGaussianBlur() BlurDrawer {
+	return &gaussianBlur{}
+}
+
+func (g *gaussianBlur) DrawBlur(canvas draw.Image, kernelSize int, sigma float64) error {
+	kernel := g.generateGaussianKernel(kernelSize, sigma)
+	bounds := canvas.Bounds()
+
+	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+		for x := bounds.Min.X; x < bounds.Max.X; x++ {
+			r, g, b := g.applyKernel(canvas, x, y, kernel)
+			canvas.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255})
+		}
+	}
+	return nil
+}
+
+func (g *gaussianBlur) generateGaussianKernel(kernelSize int, sigma float64) [][]float64 {
+	kernel := make([][]float64, kernelSize)
+	sum := 0.0
+	mid := kernelSize / 2
+
+	for i := 0; i < kernelSize; i++ {
+		kernel[i] = make([]float64, kernelSize)
+		for j := 0; j < kernelSize; j++ {
+			x := float64(i - mid)
+			y := float64(j - mid)
+			kernel[i][j] = math.Exp(-(x*x+y*y)/(2*sigma*sigma)) / (2 * math.Pi * sigma * sigma)
+			sum += kernel[i][j]
+		}
+	}
+
+	// Normalize kernel
+	for i := 0; i < kernelSize; i++ {
+		for j := 0; j < kernelSize; j++ {
+			kernel[i][j] /= sum
+		}
+	}
+
+	return kernel
+}
+
+func (g *gaussianBlur) applyKernel(canvas draw.Image, x int, y int, kernel [][]float64) (uint8, uint8, uint8) {
+	bounds := canvas.Bounds()
+	size := len(kernel)
+	mid := size / 2
+	var r1, g1, b1 float64
+
+	for ky := 0; ky < size; ky++ {
+		for kx := 0; kx < size; kx++ {
+			px := x + kx - mid
+			py := y + ky - mid
+			if px >= bounds.Min.X && px < bounds.Max.X && py >= bounds.Min.Y && py < bounds.Max.Y {
+				rr, gg, bb, _ := canvas.At(px, py).RGBA()
+				k := kernel[ky][kx]
+				r1 += k * float64(rr>>8)
+				g1 += k * float64(gg>>8)
+				b1 += k * float64(bb>>8)
+			}
+		}
+	}
+
+	return g.clamp(r1), g.clamp(g1), g.clamp(b1)
+}
+
+func (g *gaussianBlur) clamp(value float64) uint8 {
+	if value < 0 {
+		return 0
+	}
+	if value > 255 {
+		return 255
+	}
+	return uint8(value)
+}

+ 39 - 0
gocaptcha/blur_test.go

@@ -0,0 +1,39 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/draw"
+	"testing"
+)
+
+func TestDrawBlur(t *testing.T) {
+	type args struct {
+		canvas     draw.Image
+		kernelSize int
+		sigma      float64
+	}
+	tests := []struct {
+		name    string
+		t       BlurDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    NewGaussianBlur(),
+			args: args{
+				canvas:     image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				kernelSize: 5,
+				sigma:      1.0,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawBlur(tt.args.canvas, tt.args.kernelSize, tt.args.sigma); (err != nil) != tt.wantErr {
+				t.Errorf("textDrawer.DrawString() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 129 - 0
gocaptcha/captcha.go

@@ -0,0 +1,129 @@
+package gocaptcha
+
+import (
+	"errors"
+	"image"
+	"image/color"
+	"image/draw"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io"
+	"math/rand"
+)
+
+const (
+	// DefaultDPI 默认的dpi
+	DefaultDPI = 72.0
+	// DefaultBlurKernelSize 默认模糊卷积核大小
+	DefaultBlurKernelSize = 2
+	// DefaultBlurSigma 默认模糊sigma值
+	DefaultBlurSigma = 0.65
+	// DefaultAmplitude 默认图片扭曲的振幅
+	DefaultAmplitude = 20
+	//DefaultFrequency 默认图片扭曲的波频率
+	DefaultFrequency = 0.05
+)
+
+//var TextCharacters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+
+const (
+	ImageFormatPng ImageFormat = iota
+	ImageFormatJpeg
+	ImageFormatGif
+)
+
+// ImageFormat 图片格式
+type ImageFormat int
+
+type CaptchaImage struct {
+	nrgba   *image.NRGBA
+	width   int
+	height  int
+	Complex int
+	Error   error
+}
+
+// New 新建一个图片对象
+func New(width int, height int, bgColor color.RGBA) *CaptchaImage {
+	m := image.NewNRGBA(image.Rect(0, 0, width, height))
+
+	draw.Draw(m, m.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src)
+
+	return &CaptchaImage{
+		nrgba:  m,
+		height: height,
+		width:  width,
+	}
+}
+
+// Encode 编码图片
+func (captcha *CaptchaImage) Encode(w io.Writer, imageFormat ImageFormat) error {
+
+	if imageFormat == ImageFormatPng {
+		return png.Encode(w, captcha.nrgba)
+	}
+	if imageFormat == ImageFormatJpeg {
+		return jpeg.Encode(w, captcha.nrgba, &jpeg.Options{Quality: 100})
+	}
+	if imageFormat == ImageFormatGif {
+		return gif.Encode(w, captcha.nrgba, &gif.Options{NumColors: 256})
+	}
+
+	return errors.New("not supported image format")
+}
+
+// DrawLine 画直线.
+func (captcha *CaptchaImage) DrawLine(drawer LineDrawer, lineColor color.Color) *CaptchaImage {
+	if captcha.Error != nil {
+		return captcha
+	}
+	y := captcha.nrgba.Bounds().Dy()
+	point1 := image.Point{X: captcha.nrgba.Bounds().Min.X + 1, Y: rand.Intn(y)}
+	point2 := image.Point{X: captcha.nrgba.Bounds().Max.X - 1, Y: rand.Intn(y)}
+	captcha.Error = drawer.DrawLine(captcha.nrgba, point1, point2, lineColor)
+	return captcha
+}
+
+// DrawBorder 画边框.
+func (captcha *CaptchaImage) DrawBorder(borderColor color.RGBA) *CaptchaImage {
+	if captcha.Error != nil {
+		return captcha
+	}
+	for x := 0; x < captcha.width; x++ {
+		captcha.nrgba.Set(x, 0, borderColor)
+		captcha.nrgba.Set(x, captcha.height-1, borderColor)
+	}
+	for y := 0; y < captcha.height; y++ {
+		captcha.nrgba.Set(0, y, borderColor)
+		captcha.nrgba.Set(captcha.width-1, y, borderColor)
+	}
+	return captcha
+}
+
+// DrawNoise 画噪点.
+func (captcha *CaptchaImage) DrawNoise(complex NoiseDensity, noiseDrawer NoiseDrawer, rn int) *CaptchaImage {
+	if captcha.Error != nil {
+		return captcha
+	}
+	captcha.Error = noiseDrawer.DrawNoise(captcha.nrgba, complex, rn)
+	return captcha
+}
+
+// DrawText 写字.
+func (captcha *CaptchaImage) DrawText(textDrawer TextDrawer, text string) *CaptchaImage {
+	if captcha.Error != nil {
+		return captcha
+	}
+	captcha.Error = textDrawer.DrawString(captcha.nrgba, text)
+	return captcha
+}
+
+// DrawBlur 对图片进行模糊处理
+func (captcha *CaptchaImage) DrawBlur(drawer BlurDrawer, kernelSize int, sigma float64) *CaptchaImage {
+	if captcha.Error != nil {
+		return captcha
+	}
+	captcha.Error = drawer.DrawBlur(captcha.nrgba, kernelSize, sigma)
+	return captcha
+}

+ 24 - 0
gocaptcha/captcha_test.go

@@ -0,0 +1,24 @@
+package gocaptcha
+
+import "testing"
+
+func TestCaptchaImage_Encode(t *testing.T) {
+	err := SetFontPath("./fonts")
+	if err != nil {
+		t.Fatal(err)
+	}
+	captchaImage := New(150, 20, RandLightColor(Rn))
+	err = captchaImage.
+		DrawBorder(RandDeepColor()).
+		DrawNoise(NoiseDensityHigh, NewTextNoiseDrawer(72), Rn).
+		DrawNoise(NoiseDensityLower, NewPointNoiseDrawer(), Rn).
+		DrawLine(NewBezier3DLine(), RandDeepColor()).
+		DrawText(NewTwistTextDrawer(DefaultDPI, DefaultAmplitude, DefaultFrequency), RandText(4)).
+		DrawLine(NewBeeline(), RandDeepColor()).
+		DrawLine(NewHollowLine(), RandLightColor(Rn)).
+		DrawBlur(NewGaussianBlur(), DefaultBlurKernelSize, DefaultBlurSigma).
+		Error
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 101 - 0
gocaptcha/font.go

@@ -0,0 +1,101 @@
+package gocaptcha
+
+import (
+	"math/rand"
+	"os"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"github.com/golang/freetype"
+	"github.com/golang/freetype/truetype"
+)
+
+var DefaultFontFamily = NewFontFamily()
+var ErrNoFontsInFamily = os.ErrNotExist
+
+// SetFonts sets the default font family
+func SetFonts(fonts ...string) error {
+	for _, font := range fonts {
+		if err := DefaultFontFamily.AddFont(font); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// SetFontPath sets the default font family from a directory
+func SetFontPath(fontDirPath string) error {
+	return DefaultFontFamily.AddFontPath(fontDirPath)
+}
+
+// FontFamily is a font family that creates a new font family
+type FontFamily struct {
+	fonts     []string
+	fontCache *sync.Map
+	r         *rand.Rand
+}
+
+// Random returns a random font from the family
+func (f *FontFamily) Random() (*truetype.Font, error) {
+	if len(f.fonts) == 0 {
+		return nil, ErrNoFontsInFamily
+	}
+	fontFile := f.fonts[f.r.Intn(len(f.fonts))]
+	if v, ok := f.fontCache.Load(fontFile); ok {
+		return v.(*truetype.Font), nil
+	}
+	font, err := f.parseFont(fontFile)
+	if err != nil {
+		return nil, err
+	}
+	f.fontCache.Store(fontFile, font)
+	return font, nil
+}
+
+func (f *FontFamily) parseFont(fontFile string) (*truetype.Font, error) {
+	fontBytes, err := os.ReadFile(fontFile)
+	if err != nil {
+		return nil, err
+	}
+	font, err := freetype.ParseFont(fontBytes)
+	if err != nil {
+		return nil, err
+	}
+	return font, nil
+}
+
+// AddFont adds a font to the family and returns an error if it fails
+func (f *FontFamily) AddFont(fontFile string) error {
+	if _, ok := f.fontCache.Load(fontFile); ok {
+		return nil
+	}
+	font, err := f.parseFont(fontFile)
+	if err != nil {
+		return err
+	}
+	f.fonts = append(f.fonts, fontFile)
+	f.fontCache.Store(fontFile, font)
+	return nil
+}
+
+// AddFontPath adds all .ttf files from the given directory to the font family and returns an error if any
+func (f *FontFamily) AddFontPath(dirPath string) error {
+	return filepath.Walk(dirPath, func(path string, info os.FileInfo, walkErr error) error {
+		if walkErr != nil {
+			return walkErr
+		}
+		if !info.IsDir() && filepath.Ext(path) == ".ttf" {
+			return f.AddFont(path)
+		}
+		return nil
+	})
+}
+
+// NewFontFamily creates a new font family with the given fonts
+func NewFontFamily() *FontFamily {
+	return &FontFamily{
+		fontCache: &sync.Map{},
+		r:         rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}

+ 157 - 0
gocaptcha/font_test.go

@@ -0,0 +1,157 @@
+package gocaptcha
+
+import (
+	"testing"
+)
+
+func TestFontFamily_Random(t *testing.T) {
+	type args struct {
+	}
+	fontFamily := NewFontFamily()
+	tests := []struct {
+		name     string
+		t        *FontFamily
+		args     args
+		fn       func(*FontFamily) error
+		wantErr  bool
+		wantFont bool
+	}{
+		{
+			name: "test1",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				return nil
+			},
+			wantErr:  true,
+			wantFont: false,
+		},
+		{
+			name: "test2",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				family.fonts = append(family.fonts, "./testdata/Hiragino Sans GB.ttc")
+				return nil
+			},
+			wantErr:  true,
+			wantFont: false,
+		},
+		{
+			name: "test3",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				family.fonts = []string{"./fonts/3Dumb.ttf"}
+				return nil
+			},
+			wantErr:  false,
+			wantFont: true,
+		},
+		{
+			name: "test4",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				return family.AddFont("./fonts/3Dumb.ttf")
+			},
+			wantErr:  false,
+			wantFont: true,
+		},
+		{
+			name: "test5",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				return family.AddFont("./fonts/Comismsh.ttf")
+			},
+			wantErr:  false,
+			wantFont: true,
+		},
+		{
+			name: "test6",
+			t:    fontFamily,
+			args: args{},
+			fn: func(family *FontFamily) error {
+				return family.AddFont("./testdata/Hiragino Sans GB.ttc")
+			},
+			wantErr:  false,
+			wantFont: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_ = tt.fn(tt.t)
+
+			if font, err := tt.t.Random(); (font == nil) == tt.wantFont || (err != nil) != tt.wantErr {
+				t.Errorf("FontFamily.Random() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestSetFonts(t *testing.T) {
+	type args struct {
+		fonts []string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			args: args{
+				fonts: []string{"./fonts/3Dumb.ttf"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "test2",
+			args: args{
+				fonts: []string{"./testdata/Hiragino Sans GB.ttc"},
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := SetFonts(tt.args.fonts...); (err != nil) != tt.wantErr {
+				t.Errorf("SetFonts() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestFontFamily_AddFontPath(t *testing.T) {
+	type args struct {
+		dirPath string
+	}
+	fontFamily := NewFontFamily()
+	tests := []struct {
+		name    string
+		t       *FontFamily
+		args    args
+		wantErr bool
+	}{
+		{
+			name:    "test1",
+			t:       fontFamily,
+			args:    args{dirPath: "./fonts"},
+			wantErr: false,
+		},
+		{
+			name:    "test2",
+			t:       fontFamily,
+			args:    args{dirPath: "./testdata/not_exist"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.AddFontPath(tt.args.dirPath); (err != nil) != tt.wantErr {
+				t.Errorf("FontFamily.AddFontPath() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

BIN
gocaptcha/fonts/3Dumb.ttf


BIN
gocaptcha/fonts/D3Parallelism.ttf


BIN
gocaptcha/fonts/Flim-Flam.ttf


BIN
gocaptcha/fonts/chromohv.ttf


+ 255 - 0
gocaptcha/line.go

@@ -0,0 +1,255 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/color"
+	"image/draw"
+	"math"
+	"math/rand"
+	"time"
+)
+
+// LineDrawer 实现划线的接口
+type LineDrawer interface {
+	DrawLine(canvas draw.Image, x image.Point, y image.Point, color color.Color) error
+}
+
+type beeline struct {
+}
+
+func NewBeeline() LineDrawer {
+	return &beeline{}
+}
+
+// DrawLine 画一条直线
+func (beeline) DrawLine(canvas draw.Image, x1 image.Point, y1 image.Point, color color.Color) error {
+	dx := abs(x1.X - y1.X)
+	dy := abs(y1.Y - x1.Y)
+
+	sx, sy := 1, 1
+	if x1.X >= y1.X {
+		sx = -1
+	}
+	if x1.Y >= y1.Y {
+		sy = -1
+	}
+	err := dx - dy
+	x, y := x1.X, x1.Y
+
+	// 预定义粗线的相对偏移
+	offsets := []struct{ dx, dy int }{
+		{-1, -1}, {0, -1}, {1, -1},
+		{-1, 0}, {0, 0}, {1, 0},
+		{-1, 1}, {0, 1}, {1, 1},
+	}
+
+	// 边界检查函数
+	isValidPoint := func(cx, cy int) bool {
+		return cx >= 0 && cx < canvas.Bounds().Dx() && cy >= 0 && cy < canvas.Bounds().Dy()
+	}
+
+	// 绘制粗点函数
+	drawThickPoint := func(cx, cy int) {
+		for _, offset := range offsets {
+			nx, ny := cx+offset.dx, cy+offset.dy
+			if isValidPoint(nx, ny) {
+				canvas.Set(nx, ny, color)
+			}
+		}
+	}
+
+	// 主循环
+	for {
+		drawThickPoint(x, y) // 绘制粗点
+		if x == y1.X && y == y1.Y {
+			break // 到达终点,退出循环
+		}
+		e2 := 2 * err
+		if e2 > -dy {
+			err -= dy
+			x += sx
+		}
+		if e2 < dx {
+			err += dx
+			y += sy
+		}
+	}
+	return nil
+}
+
+type curveLine struct {
+	r *rand.Rand
+}
+
+func (c curveLine) DrawLine(canvas draw.Image, x image.Point, y image.Point, cl color.Color) error {
+	px := 0
+	var py float64 = 0
+
+	//振幅
+	amplitude := c.r.Intn(canvas.Bounds().Dy() / 2)
+
+	//Y轴方向偏移量
+	b := Random(int64(-canvas.Bounds().Dy()/4), int64(canvas.Bounds().Dy()/4))
+
+	//X轴方向偏移量
+	frequency := Random(int64(-canvas.Bounds().Dy()/4), int64(canvas.Bounds().Dy()/4))
+	// 周期
+	var t float64 = 0
+	if canvas.Bounds().Dy() > canvas.Bounds().Dx()/2 {
+		t = Random(int64(canvas.Bounds().Dx()/2), int64(canvas.Bounds().Dy()))
+	} else {
+		t = Random(int64(canvas.Bounds().Dy()), int64(canvas.Bounds().Dx()/2))
+	}
+	// 相位
+	phase := (2 * math.Pi) / t
+
+	// 曲线横坐标起始位置
+	px1 := 0
+	px2 := int(Random(int64(float64(canvas.Bounds().Dx())*0.8), int64(canvas.Bounds().Dx())))
+
+	for px = px1; px < px2; px++ {
+		if phase != 0 {
+			py = float64(amplitude)*math.Sin(phase*float64(px)+frequency) + b + (float64(canvas.Bounds().Dx()) / float64(5))
+			i := canvas.Bounds().Dy() / 5
+			for i > 0 {
+				canvas.Set(px+i, int(py), cl)
+				i--
+			}
+		}
+	}
+	return nil
+}
+
+// NewCurveLine 基于正弦函数的曲线
+func NewCurveLine() LineDrawer {
+	return &curveLine{
+		r: rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}
+
+type bezierLine struct {
+	r *rand.Rand
+}
+
+func (b bezierLine) DrawLine(canvas draw.Image, p0 image.Point, p2 image.Point, curveColor color.Color) error {
+	width := canvas.Bounds().Dx()
+	height := canvas.Bounds().Dy()
+	// 随机生成4个控制点
+	//p0 := image.Point{X: rand.Intn(width / 4), Y: rand.Intn(height)}
+	p1 := image.Point{X: b.r.Intn(width / 2), Y: b.r.Intn(height)}
+	//p2 := image.Point{X: width/2 + rand.Intn(width/4), Y: rand.Intn(height)}
+	p3 := image.Point{X: width - 1, Y: b.r.Intn(height)}
+
+	// 绘制贝塞尔曲线
+	for t := 0.0; t <= 1.0; t += 0.001 {
+		x := int((1-t)*(1-t)*(1-t)*float64(p0.X) + 3*(1-t)*(1-t)*t*float64(p1.X) + 3*(1-t)*t*t*float64(p2.X) + t*t*t*float64(p3.X))
+		y := int((1-t)*(1-t)*(1-t)*float64(p0.Y) + 3*(1-t)*(1-t)*t*float64(p1.Y) + 3*(1-t)*t*t*float64(p2.Y) + t*t*t*float64(p3.Y))
+		canvas.Set(x, y, curveColor)
+	}
+	return nil
+}
+
+// NewBezierLine 贝塞尔曲线
+func NewBezierLine() LineDrawer {
+	return &bezierLine{
+		r: rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}
+
+type bezier3DLine struct {
+	r *rand.Rand
+}
+
+// DrawLine 绘制3D效果的贝塞尔曲线
+func (b bezier3DLine) DrawLine(canvas draw.Image, p0 image.Point, p2 image.Point, cl color.Color) error {
+	width := canvas.Bounds().Dx()
+	height := canvas.Bounds().Dy()
+	// 随机生成4个控制点
+	//p0 := image.Point{X: b.r.Intn(width / 4), Y: b.r.Intn(height)}
+	p1 := image.Point{X: b.r.Intn(width / 2), Y: b.r.Intn(height)}
+	//p2 := image.Point{X: width/2 + b.r.Intn(width/4), Y: b.r.Intn(height)}
+	p3 := image.Point{X: width - 1, Y: b.r.Intn(height)}
+
+	drawPointWithWidth := func(img draw.Image, x, y int, col color.Color, width int) {
+		for dx := -width; dx <= width; dx++ {
+			for dy := -width; dy <= width; dy++ {
+				// 确保点在圆形范围内
+				if dx*dx+dy*dy <= width*width {
+					img.Set(x+dx, y+dy, col)
+				}
+			}
+		}
+	}
+	w := float64(b.r.Intn(height / 5))
+	// 绘制贝塞尔曲线,模拟3D效果
+	for t := 0.0; t <= 1.0; t += 0.001 {
+		// 计算当前点的坐标
+		x := int((1-t)*(1-t)*(1-t)*float64(p0.X) + 3*(1-t)*(1-t)*t*float64(p1.X) + 3*(1-t)*t*t*float64(p2.X) + t*t*t*float64(p3.X))
+		y := int((1-t)*(1-t)*(1-t)*float64(p0.Y) + 3*(1-t)*(1-t)*t*float64(p1.Y) + 3*(1-t)*t*t*float64(p2.Y) + t*t*t*float64(p3.Y))
+
+		// 使用 t 值调整颜色和线宽,模拟3D效果
+		opacity := uint8(255 - int(t*255)) // 透明度渐变
+		lineColor := color.NRGBA{
+			R: uint8(250 * t), // 红色分量随 t 增加
+			G: uint8(128 * (1 - t)),
+			B: 255 - uint8(128*t),
+			A: opacity,
+		}
+
+		// 模拟线宽,绘制当前点周围的像素
+		lineWidth := int(w * (1 - t)) // 线宽随 t 减小
+		drawPointWithWidth(canvas, x, y, lineColor, lineWidth)
+	}
+	return nil
+}
+
+// NewBezier3DLine 3D效果的贝塞尔曲线
+func NewBezier3DLine() LineDrawer {
+	return &bezier3DLine{
+		r: rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}
+
+type hollowLine struct {
+	r *rand.Rand
+}
+
+// DrawLine 绘制空心线
+func (h hollowLine) DrawLine(canvas draw.Image, p0 image.Point, p1 image.Point, lineColor color.Color) error {
+	bounds := canvas.Bounds()
+	width := bounds.Dx()
+	height := bounds.Dy()
+
+	x1 := float64(p0.X)
+	x2 := float64(p1.X)
+
+	multiple := float64(h.r.Intn(5)+3) / 5.0
+	if int(multiple*10)%3 == 0 {
+		multiple = multiple * -1.0
+	}
+
+	w := width / 20
+
+	for ; x1 < x2; x1++ {
+		y := math.Sin(x1*math.Pi*multiple/float64(width)) * float64(height/3)
+
+		if multiple < 0 {
+			y = y + float64(height/2)
+		}
+
+		// Ensure y is within bounds
+		y = math.Max(0, math.Min(float64(height-1), y))
+
+		for i := 0; i <= w && int(y)+i < height; i++ {
+			canvas.Set(int(x1), int(y)+i, lineColor)
+		}
+	}
+	return nil
+}
+
+// NewHollowLine 空心线
+func NewHollowLine() LineDrawer {
+	return &hollowLine{
+		r: rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}

+ 144 - 0
gocaptcha/line_test.go

@@ -0,0 +1,144 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/color"
+	"image/draw"
+	"testing"
+)
+
+func Test_Beeline(t *testing.T) {
+	type args struct {
+		canvas    draw.Image
+		x         image.Point
+		y         image.Point
+		lineColor color.Color
+	}
+	tests := []struct {
+		name    string
+		t       LineDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    NewBeeline(),
+			args: args{
+				canvas:    image.NewRGBA(image.Rect(0, 0, 10, 10)),
+				x:         image.Point{X: 11, Y: 5},
+				y:         image.Point{X: 10, Y: 5},
+				lineColor: color.RGBA{},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawLine(tt.args.canvas, tt.args.x, tt.args.y, tt.args.lineColor); (err != nil) != tt.wantErr {
+				t.Errorf("beeline.DrawLine() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_CurveLine(t *testing.T) {
+	type args struct {
+		canvas    draw.Image
+		x         image.Point
+		y         image.Point
+		lineColor color.Color
+	}
+	tests := []struct {
+		name    string
+		t       LineDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    NewCurveLine(),
+			args: args{
+				canvas:    image.NewRGBA(image.Rect(0, 0, 10, 10)),
+				x:         image.Point{X: 11, Y: 5},
+				y:         image.Point{X: 10, Y: 5},
+				lineColor: color.RGBA{},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawLine(tt.args.canvas, tt.args.x, tt.args.y, tt.args.lineColor); (err != nil) != tt.wantErr {
+				t.Errorf("beeline.DrawLine() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_BezierLine(t *testing.T) {
+	type args struct {
+		canvas    draw.Image
+		x         image.Point
+		y         image.Point
+		lineColor color.Color
+	}
+	tests := []struct {
+		name    string
+		t       LineDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    NewBezierLine(),
+			args: args{
+				canvas:    image.NewRGBA(image.Rect(0, 0, 10, 10)),
+				x:         image.Point{X: 11, Y: 5},
+				y:         image.Point{X: 10, Y: 5},
+				lineColor: color.RGBA{},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawLine(tt.args.canvas, tt.args.x, tt.args.y, tt.args.lineColor); (err != nil) != tt.wantErr {
+				t.Errorf("beeline.DrawLine() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_HollowLine(t *testing.T) {
+	type args struct {
+		canvas    draw.Image
+		x         image.Point
+		y         image.Point
+		lineColor color.Color
+	}
+	tests := []struct {
+		name    string
+		t       LineDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    NewHollowLine(),
+			args: args{
+				canvas:    image.NewRGBA(image.Rect(0, 0, 10, 10)),
+				x:         image.Point{X: 1, Y: 5},
+				y:         image.Point{X: 10, Y: 5},
+				lineColor: color.RGBA{},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawLine(tt.args.canvas, tt.args.x, tt.args.y, tt.args.lineColor); (err != nil) != tt.wantErr {
+				t.Errorf("beeline.DrawLine() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 135 - 0
gocaptcha/noise.go

@@ -0,0 +1,135 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/draw"
+	"math/rand"
+	"time"
+
+	"github.com/golang/freetype"
+	"golang.org/x/image/font"
+)
+
+// NoiseDensity is the complexity of captcha
+type NoiseDensity int
+
+const (
+	NoiseDensityLower NoiseDensity = iota
+	NoiseDensityMedium
+	NoiseDensityHigh
+)
+
+// NoiseDrawer is a type that can make noise on an image
+type NoiseDrawer interface {
+	// DrawNoise draws noise on the image
+	DrawNoise(img draw.Image, density NoiseDensity, rn int) error
+}
+
+type pointNoiseDrawer struct {
+	r *rand.Rand
+}
+
+// DrawNoise draws noise on the image
+func (n pointNoiseDrawer) DrawNoise(img draw.Image, density NoiseDensity, rn int) error {
+	var densityNum int
+	switch density {
+	case NoiseDensityLower:
+		densityNum = 28
+	case NoiseDensityMedium:
+		densityNum = 18
+	case NoiseDensityHigh:
+		densityNum = 8
+	default:
+		densityNum = 18
+	}
+
+	maxSize := (img.Bounds().Dy() * img.Bounds().Dx()) / densityNum
+
+	bounds := img.Bounds()
+	width := bounds.Dx()
+	height := bounds.Dy()
+
+	for i := 0; i < maxSize; i++ {
+		rw := n.r.Intn(width)
+		rh := n.r.Intn(height)
+
+		img.Set(rw, rh, RandColor())
+		// 优化噪声点的生成逻辑,例如可以基于一定的概率决定是否绘制额外的点
+		if n.r.Intn(3) == 0 && rw+1 < width && rh+1 < height {
+			img.Set(rw+1, rh+1, RandColor())
+		}
+	}
+	return nil
+}
+
+// NewPointNoiseDrawer returns a NoiseDrawer that draws noise points
+func NewPointNoiseDrawer() NoiseDrawer {
+	return &pointNoiseDrawer{
+		r: rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}
+
+// textNoiseDrawer draws noise text
+type textNoiseDrawer struct {
+	r   *rand.Rand
+	dpi float64
+}
+
+// DrawNoise draws noise on the image
+func (n textNoiseDrawer) DrawNoise(img draw.Image, density NoiseDensity, rn int) error {
+	var densityNum int
+	switch density {
+	case NoiseDensityLower:
+		densityNum = 2000
+	case NoiseDensityMedium:
+		densityNum = 1500
+	case NoiseDensityHigh:
+		densityNum = 1000
+	default:
+		densityNum = 1500 // 默认值
+	}
+	bounds := img.Bounds()
+	maxSize := (bounds.Dy() * bounds.Dx()) / densityNum
+	c := freetype.NewContext()
+	if n.dpi <= 0 {
+		n.dpi = 72
+	}
+
+	c.SetDPI(n.dpi)
+
+	c.SetClip(bounds)
+	c.SetDst(img)
+	c.SetHinting(font.HintingFull)
+	rawFontSize := float64(bounds.Dy()) / (1 + float64(n.r.Intn(7))/float64(10))
+
+	for i := 0; i < maxSize; i++ {
+
+		rw := n.r.Intn(bounds.Dx())
+		rh := n.r.Intn(bounds.Dy())
+
+		text := RandText(1)
+		fontSize := rawFontSize/2 + float64(n.r.Intn(5))
+
+		c.SetSrc(image.NewUniform(RandLightColor(rn)))
+		c.SetFontSize(fontSize)
+		f, err := DefaultFontFamily.Random()
+		if err != nil {
+			return err
+		}
+		c.SetFont(f)
+		pt := freetype.Pt(rw, rh)
+
+		_, err = c.DrawString(text, pt)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func NewTextNoiseDrawer(dpi float64) NoiseDrawer {
+	return &textNoiseDrawer{
+		r:   rand.New(rand.NewSource(time.Now().UnixNano())),
+		dpi: dpi,
+	}
+}

+ 128 - 0
gocaptcha/noise_test.go

@@ -0,0 +1,128 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/draw"
+	"math/rand"
+	"testing"
+)
+
+func Test_PointNoiseDrawer(t *testing.T) {
+	type args struct {
+		canvas  draw.Image
+		density NoiseDensity
+	}
+	tests := []struct {
+		name    string
+		t       NoiseDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    &pointNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityLower,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test2",
+			t:    &pointNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityMedium,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test3",
+			t:    &pointNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityHigh,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test4",
+			t:    NewPointNoiseDrawer(),
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensity(4),
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawNoise(tt.args.canvas, tt.args.density, Rn); (err != nil) != tt.wantErr {
+				t.Errorf("pointNoiseDrawer.DrawNoise() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_TextNoiseDrawer(t *testing.T) {
+	type args struct {
+		canvas  draw.Image
+		density NoiseDensity
+	}
+	tests := []struct {
+		name    string
+		t       NoiseDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "test1",
+			t:    &textNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityLower,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test2",
+			t:    &textNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityMedium,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test3",
+			t:    &textNoiseDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensityHigh,
+			},
+			wantErr: false,
+		},
+		{
+			name: "test4",
+			t:    NewTextNoiseDrawer(72),
+			args: args{
+				canvas:  image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				density: NoiseDensity(4),
+			},
+			wantErr: false,
+		},
+	}
+
+	err := DefaultFontFamily.AddFontPath("./fonts")
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawNoise(tt.args.canvas, tt.args.density, Rn); (err != nil) != tt.wantErr {
+				t.Errorf("textNoiseDrawer.DrawNoise() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 100 - 0
gocaptcha/random.go

@@ -0,0 +1,100 @@
+// Copyright 2011-2014 Dmitry Chestnykh. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package gocaptcha
+
+import (
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"io"
+)
+
+// idLen is a length of captcha id string.
+// (20 bytes of 62-letter alphabet give ~119 bits.)
+const idLen = 20
+
+// idChars are characters allowed in captcha id.
+var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+
+// rngKey is a secret key used to deterministically derive seeds for
+// PRNGs used in image and audio. Generated once during initialization.
+var rngKey [32]byte
+
+func init() {
+	if _, err := io.ReadFull(rand.Reader, rngKey[:]); err != nil {
+		panic("captcha: error reading random source: " + err.Error())
+	}
+}
+
+// Purposes for seed derivation. The goal is to make deterministic PRNG produce
+// different outputs for images and audio by using different derived seeds.
+const (
+	imageSeedPurpose = 0x01
+	audioSeedPurpose = 0x02
+)
+
+// deriveSeed returns a 16-byte PRNG seed from rngKey, purpose, id and digits.
+// Same purpose, id and digits will result in the same derived seed for this
+// instance of running application.
+//
+//	out = HMAC(rngKey, purpose || id || 0x00 || digits)  (cut to 16 bytes)
+func deriveSeed(purpose byte, id string, digits []byte) (out [16]byte) {
+	var buf [sha256.Size]byte
+	h := hmac.New(sha256.New, rngKey[:])
+	h.Write([]byte{purpose})
+	io.WriteString(h, id)
+	h.Write([]byte{0})
+	h.Write(digits)
+	sum := h.Sum(buf[:0])
+	copy(out[:], sum)
+	return
+}
+
+// RandomDigits returns a byte slice of the given length containing
+// pseudorandom numbers in range 0-9. The slice can be used as a captcha
+// solution.
+func RandomDigits(length int) []byte {
+	return randomBytesMod(length, 10)
+}
+
+// randomBytes returns a byte slice of the given length read from CSPRNG.
+func randomBytes(length int) (b []byte) {
+	b = make([]byte, length)
+	if _, err := io.ReadFull(rand.Reader, b); err != nil {
+		panic("captcha: error reading random source: " + err.Error())
+	}
+	return
+}
+
+// randomBytesMod returns a byte slice of the given length, where each byte is
+// a random number modulo mod.
+func randomBytesMod(length int, mod byte) (b []byte) {
+	b = make([]byte, length)
+	maxrb := byte(256 - (256 % int(mod)))
+	i := 0
+	for {
+		r := randomBytes(length + (length / 4))
+		for _, c := range r {
+			if c > maxrb {
+				// Skip this number to avoid modulo bias.
+				continue
+			}
+			b[i] = c % mod
+			i++
+			if i == length {
+				return
+			}
+		}
+	}
+}
+
+// randomId returns a new random id string.
+func randomId() string {
+	b := randomBytesMod(idLen, byte(len(idChars)))
+	for i, c := range b {
+		b[i] = idChars[c]
+	}
+	return string(b)
+}

+ 24 - 0
gocaptcha/rnd.go

@@ -0,0 +1,24 @@
+package gocaptcha
+
+import (
+	"fmt"
+	"math/rand"
+)
+
+// Random 生成指定大小的随机数.
+func Random(min int64, max int64) float64 {
+	if max <= min {
+		panic(fmt.Sprintf("invalid range %d <= %d", max, min)) // 修复了错误消息的顺序
+	}
+
+	rangeSize := max - min
+	var randomValue int64
+
+	if rangeSize > 0 {
+		randomValue = rand.Int63n(rangeSize) + min // 确保在[min, max)范围内
+	} else { // 处理负数范围
+		randomValue = rand.Int63n(-rangeSize) + min // 确保在[min, max)范围内,此时max是更小的负数
+	}
+
+	return float64(randomValue) + rand.Float64() // 添加小数部分
+}

+ 11 - 0
gocaptcha/rnd_test.go

@@ -0,0 +1,11 @@
+package gocaptcha
+
+import (
+	"testing"
+)
+
+func TestRandom(t *testing.T) {
+	for i := 0; i < 100; i++ {
+		t.Log(Random(0, 1))
+	}
+}

+ 121 - 0
gocaptcha/start.go

@@ -0,0 +1,121 @@
+package gocaptcha
+
+import (
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+	"app.yhyue.com/moapp/jybase/redis"
+	"fmt"
+	"image/color"
+	"log"
+	"math/rand"
+	"net/http"
+	"time"
+)
+
+// 图形验证码大小
+const (
+	dx         = 200
+	dy         = 80
+	fontSize   = 75
+	Rn         = 1
+	CollectNum = 50
+	Expiration = 10 * time.Minute
+)
+
+// 生成的字符集
+var (
+	TextCharacters = []rune("ACDEFGHJKLMNPQRSTUVWXY2456789")
+	globalStore    = NewMemoryStore(CollectNum, Expiration)
+)
+
+// 初始化字体文件
+func InitCaptcha() {
+	err := SetFontPath("./fonts/")
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+// RandText 生成随机字体.
+func RandText(num int) string {
+	textNum := len(TextCharacters)
+	text := make([]rune, num)
+	for i := 0; i < num; i++ {
+		text[i] = TextCharacters[rand.Intn(textNum)]
+	}
+	return string(text)
+}
+
+// RandLightColor 随机生成浅色底色.
+func RandLightColor(n int) color.RGBA {
+	// 为每个颜色分量生成一个128到255之间的随机数
+	red := rand.Intn(100/n) + 156 + (100 - 100/n)
+	green := rand.Intn(100/n) + 156 + (100 - 100/n)
+	blue := rand.Intn(100/n) + 156 + (100 - 100/n)
+	// Alpha 通道设置为完全不透明
+	a := uint8(rand.Intn(8) + 247)
+
+	return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: a}
+}
+
+func Get(sess *httpsession.Session, checkCode string, w http.ResponseWriter, r *http.Request) {
+	code := RandText(4)
+	log.Println(code)
+	cacheKey := fmt.Sprintf("captcha_times_%s", sess.Id())
+	var (
+		// 计次数
+		times = redis.GetInt("other", cacheKey)
+		ttl   = redis.GetTTL("other", cacheKey)
+		rn    = times%6 + 1
+		//跳色
+		cc = RandLightColor(rn) //
+	)
+	// cc.A = uint8(rand.Intn(10) + 245)
+	captchaImage := New(dx, dy, cc).
+		DrawBorder(RandDeepColor())
+	if times < 3 {
+		captchaImage = captchaImage.DrawNoise(NoiseDensityLower, NewPointNoiseDrawer(), rn).
+			DrawNoise(NoiseDensityHigh, NewTextNoiseDrawer(42), rn).
+			DrawLine(NewBeeline(), RandDeepColor()).
+			DrawText(NewTwistTextDrawer(fontSize, 15, 0.04), code)
+	} else if times < 6 {
+		captchaImage = captchaImage.DrawNoise(NoiseDensityLower, NewPointNoiseDrawer(), rn).
+			DrawNoise(NoiseDensityHigh, NewTextNoiseDrawer(43), rn).
+			DrawLine(NewBeeline(), RandDeepColor()).
+			DrawText(NewTwistTextDrawer(fontSize, 14, 0.03), code).
+			DrawBlur(NewGaussianBlur(), 2, 0.15)
+	} else if times < 10 {
+		captchaImage = captchaImage.DrawNoise(NoiseDensityLower, NewPointNoiseDrawer(), rn).
+			DrawNoise(NoiseDensityHigh, NewTextNoiseDrawer(45), rn).
+			DrawLine(NewBeeline(), RandDeepColor()).
+			DrawText(NewTwistTextDrawer(fontSize, 18, 0.04), code).
+			DrawLine(NewBeeline(), RandDeepColor()).
+			DrawBlur(NewGaussianBlur(), 2, 0.41)
+
+	} else {
+		captchaImage = captchaImage.DrawNoise(NoiseDensityHigh, NewTextNoiseDrawer(50), rn).
+			DrawNoise(NoiseDensityLower, NewPointNoiseDrawer(), rn).
+			DrawLine(NewBezier3DLine(), RandLightColor(rn)).
+			DrawLine(NewBeeline(), RandDeepColor()).
+			DrawText(NewTwistTextDrawer(fontSize, 18, 0.04), code).
+			DrawBlur(NewGaussianBlur(), 2, 0.44)
+	}
+	if captchaImage.Error != nil {
+		log.Println(captchaImage.Error)
+		return
+	}
+	if times == 0 {
+		ttl = 24 * 60 * 60
+	}
+	//计次
+	times++
+	globalStore.Set(code, RandomDigits(len([]rune(code))))
+	//b := VerifyString(code, code)
+	//fmt.Println(b)
+	redis.Put("other", cacheKey, times, int(ttl))
+	sess.Set(checkCode, code)
+	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
+	w.Header().Set("Pragma", "no-cache")
+	w.Header().Set("Expires", "0")
+	w.Header().Set("Content-Type", "image/png")
+	_ = captchaImage.Encode(w, ImageFormatJpeg)
+}

+ 28 - 0
gocaptcha/start_test.go

@@ -0,0 +1,28 @@
+package gocaptcha
+
+import (
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+	"app.yhyue.com/moapp/jybase/redis"
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+)
+
+func TestCaptcha(t *testing.T) {
+	redis.InitRedisBySize("other=172.20.45.129:1712", 100, 30, 300)
+	InitCaptcha()
+	var (
+		W       http.ResponseWriter
+		R       *http.Request
+		Session = &httpsession.Session{}
+	)
+	Session.SetId("001")
+	Get(Session, "--", W, R)
+	time.Sleep(10 * time.Second)
+}
+
+func TestVerify(t *testing.T) {
+	b := VerifyString("JHPC", "JHPC")
+	fmt.Println(b)
+}

+ 117 - 0
gocaptcha/store.go

@@ -0,0 +1,117 @@
+// Copyright 2011 Dmitry Chestnykh. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package gocaptcha
+
+import (
+	"container/list"
+	"sync"
+	"time"
+)
+
+// An object implementing Store interface can be registered with SetCustomStore
+// function to handle storage and retrieval of captcha ids and solutions for
+// them, replacing the default memory store.
+//
+// It is the responsibility of an object to delete expired and used captchas
+// when necessary (for example, the default memory store collects them in Set
+// method after the certain amount of captchas has been stored.)
+type Store interface {
+	// Set sets the digits for the captcha id.
+	Set(id string, digits []byte)
+
+	// Get returns stored digits for the captcha id. Clear indicates
+	// whether the captcha must be deleted from the store.
+	Get(id string, clear bool) (digits []byte)
+}
+
+// expValue stores timestamp and id of captchas. It is used in the list inside
+// memoryStore for indexing generated captchas by timestamp to enable garbage
+// collection of expired captchas.
+type idByTimeValue struct {
+	timestamp time.Time
+	id        string
+}
+
+// memoryStore is an internal store for captcha ids and their values.
+type memoryStore struct {
+	sync.RWMutex
+	digitsById map[string][]byte
+	idByTime   *list.List
+	// Number of items stored since last collection.
+	numStored int
+	// Number of saved items that triggers collection.
+	collectNum int
+	// Expiration time of captchas.
+	expiration time.Duration
+}
+
+// NewMemoryStore returns a new standard memory store for captchas with the
+// given collection threshold and expiration time (duration). The returned
+// store must be registered with SetCustomStore to replace the default one.
+func NewMemoryStore(collectNum int, expiration time.Duration) Store {
+	s := new(memoryStore)
+	s.digitsById = make(map[string][]byte)
+	s.idByTime = list.New()
+	s.collectNum = collectNum
+	s.expiration = expiration
+	return s
+}
+
+func (s *memoryStore) Set(id string, digits []byte) {
+	s.Lock()
+	s.digitsById[id] = digits
+	s.idByTime.PushBack(idByTimeValue{time.Now(), id})
+	s.numStored++
+	if s.numStored <= s.collectNum {
+		s.Unlock()
+		return
+	}
+	s.Unlock()
+	go s.collect()
+}
+
+func (s *memoryStore) Get(id string, clear bool) (digits []byte) {
+	if !clear {
+		// When we don't need to clear captcha, acquire read lock.
+		s.RLock()
+		defer s.RUnlock()
+	} else {
+		s.Lock()
+		defer s.Unlock()
+	}
+	digits, ok := s.digitsById[id]
+	if !ok {
+		return
+	}
+	if clear {
+		delete(s.digitsById, id)
+		// XXX(dchest) Index (s.idByTime) will be cleaned when
+		// collecting expired captchas.  Can't clean it here, because
+		// we don't store reference to expValue in the map.
+		// Maybe store it?
+	}
+	return
+}
+
+func (s *memoryStore) collect() {
+	s.Lock()
+	defer s.Unlock()
+	s.numStored = 0
+	now := time.Now()
+	for e := s.idByTime.Front(); e != nil; {
+		ev, ok := e.Value.(idByTimeValue)
+		if !ok {
+			return
+		}
+		if ev.timestamp.Add(s.expiration).Before(now) {
+			delete(s.digitsById, ev.id)
+			next := e.Next()
+			s.idByTime.Remove(e)
+			e = next
+		} else {
+			return
+		}
+	}
+}

+ 79 - 0
gocaptcha/store_test.go

@@ -0,0 +1,79 @@
+// Copyright 2011 Dmitry Chestnykh. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package gocaptcha
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestSetGet(t *testing.T) {
+	s := NewMemoryStore(CollectNum, Expiration)
+	id := "captcha id"
+	d := RandomDigits(10)
+	s.Set(id, d)
+	d2 := s.Get(id, false)
+	if d2 == nil || !bytes.Equal(d, d2) {
+		t.Errorf("saved %v, getDigits returned got %v", d, d2)
+	}
+}
+
+func TestGetClear(t *testing.T) {
+	s := NewMemoryStore(CollectNum, Expiration)
+	id := "captcha id"
+	d := RandomDigits(10)
+	s.Set(id, d)
+	d2 := s.Get(id, true)
+	if d2 == nil || !bytes.Equal(d, d2) {
+		t.Errorf("saved %v, getDigitsClear returned got %v", d, d2)
+	}
+	d2 = s.Get(id, false)
+	if d2 != nil {
+		t.Errorf("getDigitClear didn't clear (%q=%v)", id, d2)
+	}
+}
+
+func TestCollect(t *testing.T) {
+	//TODO(dchest): can't test automatic collection when saving, because
+	//it's currently launched in a different goroutine.
+	s := NewMemoryStore(10, -1)
+	// create 10 ids
+	ids := make([]string, 10)
+	d := RandomDigits(10)
+	for i := range ids {
+		ids[i] = randomId()
+		s.Set(ids[i], d)
+	}
+	s.(*memoryStore).collect()
+	// Must be already collected
+	nc := 0
+	for i := range ids {
+		d2 := s.Get(ids[i], false)
+		if d2 != nil {
+			t.Errorf("%d: not collected", i)
+			nc++
+		}
+	}
+	if nc > 0 {
+		t.Errorf("= not collected %d out of %d captchas", nc, len(ids))
+	}
+}
+
+func BenchmarkSetCollect(b *testing.B) {
+	b.StopTimer()
+	d := RandomDigits(10)
+	s := NewMemoryStore(9999, -1)
+	ids := make([]string, 1000)
+	for i := range ids {
+		ids[i] = randomId()
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		for j := 0; j < 1000; j++ {
+			s.Set(ids[j], d)
+		}
+		s.(*memoryStore).collect()
+	}
+}

BIN
gocaptcha/testdata/Hiragino Sans GB.ttc


+ 173 - 0
gocaptcha/text.go

@@ -0,0 +1,173 @@
+package gocaptcha
+
+import (
+	"errors"
+	"image"
+	"image/draw"
+	"math"
+	"math/rand"
+	"time"
+
+	"github.com/golang/freetype"
+	"golang.org/x/image/font"
+)
+
+var (
+	ErrNilCanvas = errors.New("canvas is nil")
+	ErrNilText   = errors.New("text is nil")
+)
+
+// TextDrawer is a text drawer interface.
+type TextDrawer interface {
+	DrawString(canvas draw.Image, text string) error
+}
+
+type textDrawer struct {
+	dpi float64
+	r   *rand.Rand
+}
+
+// DrawString draws a string on the canvas.
+func (t *textDrawer) DrawString(canvas draw.Image, text string) error {
+	if len(text) == 0 {
+		return ErrNilText
+	}
+	if canvas == nil {
+		return ErrNilCanvas
+	}
+	c := freetype.NewContext()
+	if t.dpi <= 0 {
+		t.dpi = 72
+	}
+	c.SetDPI(t.dpi)
+	c.SetClip(canvas.Bounds())
+	c.SetDst(canvas)
+	c.SetHinting(font.HintingFull)
+
+	fontWidth := canvas.Bounds().Dx() / len(text)
+
+	for i, s := range text {
+
+		fontSize := float64(canvas.Bounds().Dy()) / (1 + float64(t.r.Intn(7))/float64(9))
+
+		c.SetSrc(image.NewUniform(RandDeepColor()))
+		c.SetFontSize(fontSize)
+		f, err := DefaultFontFamily.Random()
+
+		if err != nil {
+			return err
+		}
+		c.SetFont(f)
+
+		x := (fontWidth)*i + (fontWidth)/int(fontSize)
+
+		y := 5 + t.r.Intn(canvas.Bounds().Dy()/2) + int(fontSize/2)
+
+		pt := freetype.Pt(x, y)
+
+		_, err = c.DrawString(string(s), pt)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// NewTextDrawer returns a new text drawer.
+func NewTextDrawer(dpi float64) TextDrawer {
+	return &textDrawer{
+		dpi: dpi,
+		r:   rand.New(rand.NewSource(time.Now().UnixNano())),
+	}
+}
+
+type twistTextDrawer struct {
+	dpi       float64
+	r         *rand.Rand
+	amplitude float64
+	frequency float64
+}
+
+// DrawString draws a string on the canvas.
+func (t *twistTextDrawer) DrawString(canvas draw.Image, text string) error {
+	if len(text) == 0 {
+		return ErrNilText
+	}
+	if canvas == nil {
+		return ErrNilCanvas
+	}
+	// 创建一个新的画布用于存储扭曲后的图像
+	textCanvas := image.NewRGBA(image.Rect(0, 0, canvas.Bounds().Dx(), canvas.Bounds().Dy()))
+	draw.Draw(textCanvas, textCanvas.Bounds(), image.Transparent, image.Point{}, draw.Src)
+
+	c := freetype.NewContext()
+	if t.dpi <= 0 {
+		t.dpi = 72
+	}
+	c.SetDPI(t.dpi)
+	c.SetClip(canvas.Bounds())
+	c.SetDst(textCanvas)
+	c.SetHinting(font.HintingFull)
+
+	fontWidth := canvas.Bounds().Dx() / len(text)
+
+	for i, s := range text {
+
+		fontSize := float64(canvas.Bounds().Dy()) / (1 + float64(t.r.Intn(7))/float64(9))
+
+		c.SetSrc(image.NewUniform(RandDeepColor()))
+		c.SetFontSize(fontSize)
+		f, err := DefaultFontFamily.Random()
+
+		if err != nil {
+			return err
+		}
+		c.SetFont(f)
+
+		x := (fontWidth)*i + (fontWidth)/int(fontSize)
+
+		y := 5 + t.r.Intn(canvas.Bounds().Dy()/2) + int(fontSize/2)
+
+		pt := freetype.Pt(x, y)
+
+		_, err = c.DrawString(string(s), pt)
+		if err != nil {
+			return err
+		}
+	}
+	return t.twistEffect(textCanvas, canvas)
+}
+
+func (t *twistTextDrawer) twistEffect(src image.Image, dst draw.Image) error {
+	width := src.Bounds().Dx()
+	height := src.Bounds().Dy()
+
+	// 遍历源图像像素
+	for y := 0; y < height; y++ {
+		for x := 0; x < width; x++ {
+			// 计算扭曲后的坐标
+			dx := int(t.amplitude * math.Sin(t.frequency*float64(y)))
+			newX := x + dx
+			newY := y
+
+			// 如果新坐标在目标图像范围内,设置像素
+			if newX >= 0 && newX < width && newY >= 0 && newY < height {
+				_, _, _, a := src.At(x, y).RGBA()
+				if a != 0 {
+					dst.Set(newX, newY, src.At(x, y))
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// NewTwistTextDrawer returns a new text drawer with twist effect.
+func NewTwistTextDrawer(dpi float64, amplitude float64, frequency float64) TextDrawer {
+	return &twistTextDrawer{
+		dpi:       dpi,
+		r:         rand.New(rand.NewSource(time.Now().UnixNano())),
+		amplitude: amplitude,
+		frequency: frequency,
+	}
+}

+ 112 - 0
gocaptcha/text_test.go

@@ -0,0 +1,112 @@
+package gocaptcha
+
+import (
+	"image"
+	"image/draw"
+	"math/rand"
+	"testing"
+)
+
+func Test_textDrawer_DrawString(t *testing.T) {
+	type args struct {
+		canvas draw.Image
+		text   string
+	}
+	tests := []struct {
+		name    string
+		t       *textDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "Successful DrawString",
+			t:    &textDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				text:   "Hello, World!",
+			},
+			wantErr: false,
+		},
+		{
+			name: "DrawString with empty text",
+			t:    &textDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				text:   "",
+			},
+			wantErr: true,
+		},
+		{
+			name: "DrawString with nil canvas",
+			t:    &textDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: nil,
+				text:   "Hello, World!",
+			},
+			wantErr: true,
+		},
+	}
+	err := DefaultFontFamily.AddFontPath("./fonts")
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawString(tt.args.canvas, tt.args.text); (err != nil) != tt.wantErr {
+				t.Errorf("textDrawer.DrawString() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_twistTextDrawer_DrawString(t *testing.T) {
+	type args struct {
+		canvas draw.Image
+		text   string
+	}
+	tests := []struct {
+		name    string
+		t       *twistTextDrawer
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "Successful DrawString",
+			t:    &twistTextDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				text:   "Hello, World!",
+			},
+			wantErr: false,
+		},
+		{
+			name: "DrawString with empty text",
+			t:    &twistTextDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: image.NewRGBA(image.Rect(0, 0, 100, 100)),
+				text:   "",
+			},
+			wantErr: true,
+		},
+		{
+			name: "DrawString with nil canvas",
+			t:    &twistTextDrawer{r: rand.New(rand.NewSource(1))},
+			args: args{
+				canvas: nil,
+				text:   "Hello, World!",
+			},
+			wantErr: true,
+		},
+	}
+	err := DefaultFontFamily.AddFontPath("./fonts")
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.t.DrawString(tt.args.canvas, tt.args.text); (err != nil) != tt.wantErr {
+				t.Errorf("twistTextDrawer.DrawString() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 99 - 0
gocaptcha/utils.go

@@ -0,0 +1,99 @@
+package gocaptcha
+
+import (
+	"image/color"
+	"math/rand"
+)
+
+// RandDeepColor 随机生成深色系.
+func RandDeepColor() color.RGBA {
+	// 限制 RGB 最大值为 150 (深色系),最小值为 50
+	maxValue := 150
+	minValue := 50
+
+	r := uint8(rand.Intn(maxValue-minValue+1) + minValue)
+	g := uint8(rand.Intn(maxValue-minValue+1) + minValue)
+	b := uint8(rand.Intn(maxValue-minValue+1) + minValue)
+
+	// Alpha 通道设置为完全不透明
+	a := uint8(rand.Intn(256))
+
+	return color.RGBA{R: r, G: g, B: b, A: a}
+}
+
+// RandLightColor 随机生成浅色.
+//func RandLightColor() color.RGBA {
+//	// 为每个颜色分量生成一个128到255之间的随机数
+//	red := rand.Intn(128) + 128
+//	green := rand.Intn(128) + 128
+//	blue := rand.Intn(128) + 128
+//	// Alpha 通道设置为完全不透明
+//	a := uint8(rand.Intn(256))
+//
+//	return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: a}
+//}
+
+// RandColor 生成随机颜色.
+func RandColor() color.RGBA {
+	red := rand.Intn(255)
+	green := rand.Intn(255)
+	var blue int
+
+	// Calculate blue value based on the sum of red and green
+	sum := red + green
+	if sum > 400 {
+		blue = 0
+	} else {
+		blueTemp := 400 - sum
+		blue = max(0, min(255, blueTemp))
+	}
+	return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: 255}
+}
+// RandText 生成随机字体.
+//func RandText(num int) string {
+//	textNum := len(TextCharacters)
+//	text := make([]rune, num)
+//	for i := 0; i < num; i++ {
+//		text[i] = TextCharacters[rand.Intn(textNum)]
+//	}
+//	return string(text)
+//}
+
+// ColorToRGB 颜色代码转换为RGB
+// input int
+// output int red, green, blue.
+func ColorToRGB(colorVal int) color.RGBA {
+
+	red := colorVal >> 16
+	green := (colorVal & 0x00FF00) >> 8
+	blue := colorVal & 0x0000FF
+
+	return color.RGBA{
+		R: uint8(red),
+		G: uint8(green),
+		B: uint8(blue),
+		A: uint8(255),
+	}
+}
+
+// 整数绝对值函数
+func abs[T ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64](a T) T {
+	var zero T
+	if a < zero {
+		return -a
+	}
+	return a
+}
+
+func max(x,y int) int{
+	if x<y{
+		return y
+	}
+	return x
+}
+func min(x , y int) int{
+	if x>y{
+		return y
+	}
+	return x
+}

+ 215 - 0
gocaptcha/utils_test.go

@@ -0,0 +1,215 @@
+package gocaptcha
+
+import (
+	"image/color"
+	"testing"
+)
+
+func Test_abs(t *testing.T) {
+	type args struct {
+		a int
+	}
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "Positive number",
+			args: args{a: 10},
+			want: 10,
+		},
+		{
+			name: "Negative number",
+			args: args{a: -10},
+			want: 10,
+		},
+		{
+			name: "Zero",
+			args: args{a: 0},
+			want: 0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := abs(tt.args.a); got != tt.want {
+				t.Errorf("abs() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestColorToRGB(t *testing.T) {
+	type args struct {
+		colorVal int
+	}
+	tests := []struct {
+		name string
+		args args
+		want color.RGBA
+	}{
+		{
+			name: "Test 1",
+			args: args{colorVal: 0xFF0000},
+			want: color.RGBA{R: 255, G: 0, B: 0, A: 255},
+		},
+		{
+			name: "Test 2",
+			args: args{colorVal: 0x00FF00},
+			want: color.RGBA{R: 0, G: 255, B: 0, A: 255},
+		},
+		{
+			name: "Test 3",
+			args: args{colorVal: 0x0000FF},
+			want: color.RGBA{R: 0, G: 0, B: 255, A: 255},
+		},
+		{
+			name: "Test 4",
+			args: args{colorVal: 0xFFFFFF},
+			want: color.RGBA{R: 255, G: 255, B: 255, A: 255},
+		},
+		{
+			name: "Test 5",
+			args: args{colorVal: 0x000000},
+			want: color.RGBA{R: 0, G: 0, B: 0, A: 255},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := ColorToRGB(tt.args.colorVal); got != tt.want {
+				t.Errorf("ColorToRGB() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestRandDeepColor(t *testing.T) {
+	type args struct {
+		colorVal int
+	}
+	tests := []struct {
+		name string
+		args args
+		want color.RGBA
+	}{
+		{
+			name: "Test 1",
+			args: args{colorVal: 0xFF0000},
+			want: color.RGBA{R: 50, G: 50, B: 50, A: 0},
+		},
+		{
+			name: "Test 2",
+			args: args{colorVal: 0x00FF00},
+			want: color.RGBA{R: 0, G: 50, B: 0, A: 0},
+		},
+		{
+			name: "Test 3",
+			args: args{colorVal: 0x0000FF},
+			want: color.RGBA{R: 0, G: 0, B: 50, A: 50},
+		},
+		{
+			name: "Test 4",
+			args: args{colorVal: 0xFFFFFF},
+			want: color.RGBA{R: 50, G: 50, B: 50, A: 50},
+		},
+		{
+			name: "Test 5",
+			args: args{colorVal: 0x000000},
+			want: color.RGBA{R: 0, G: 0, B: 0, A: 255},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := RandDeepColor(); got.R <= tt.want.R && got.G <= tt.want.G && got.B <= tt.want.B {
+				t.Errorf("RandDeepColor() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestRandLightColor(t *testing.T) {
+	type args struct {
+		colorVal int
+	}
+	tests := []struct {
+		name string
+		args args
+		want color.RGBA
+	}{
+		{
+			name: "Test 1",
+			args: args{colorVal: 0xFF0000},
+			want: color.RGBA{R: 128, G: 128, B: 128, A: 255},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := RandLightColor(Rn); got.R <= tt.want.R && got.G <= tt.want.G && got.B <= tt.want.B {
+				t.Errorf("RandLightColor() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestRandColor(t *testing.T) {
+	type args struct {
+		colorVal int
+	}
+	tests := []struct {
+		name string
+		args args
+		want color.RGBA
+	}{
+		{
+			name: "Test 1",
+			args: args{colorVal: 0xFF0000},
+			want: color.RGBA{R: 255, G: 255, B: 255, A: 255},
+		},
+		{
+			name: "Test 2",
+			args: args{colorVal: 0x00FF00},
+			want: color.RGBA{R: 255, G: 255, B: 255, A: 255},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := RandColor(); got.R >= tt.want.R && got.G >= tt.want.G && got.B >= tt.want.B {
+				t.Errorf("RandColor() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestRandText(t *testing.T) {
+	type args struct {
+		num int
+	}
+	tests := []struct {
+		name string
+		args args
+		want int
+	}{
+		{
+			name: "Test 1",
+			args: args{num: 1},
+			want: 1,
+		},
+		{
+			name: "Test 2",
+			args: args{num: 2},
+			want: 2,
+		},
+		{
+			name: "Test 3",
+			args: args{num: 3},
+			want: 3,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := RandText(tt.args.num); len(got) != tt.want {
+				t.Errorf("RandText() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 53 - 0
gocaptcha/verify.go

@@ -0,0 +1,53 @@
+package gocaptcha
+
+import (
+	"bytes"
+	"strings"
+)
+
+// Verify returns true if the given digits are the ones that were used to
+// create the given captcha id.
+//
+// The function deletes the captcha with the given id from the internal
+// storage, so that the same captcha can't be verified anymore.
+func Verify(id string, digits []byte) bool {
+	if digits == nil || len(digits) == 0 {
+		return false
+	}
+	reald := globalStore.Get(id, true)
+	if reald == nil {
+		return false
+	}
+	return bytes.Equal(digits, reald)
+}
+
+// VerifyString is like Verify, but accepts a string of digits.  It removes
+// spaces and commas from the string, but any other characters, apart from
+// digits and listed above, will cause the function to return false.
+func VerifyString(id string, digits string) bool {
+	if id == "" || digits == "" {
+		return false
+	}
+	realId := globalStore.Get(id, true)
+	if realId == nil {
+		return false
+	}
+	if strings.TrimSpace(id) == strings.TrimSpace(digits) {
+		return true
+	}
+	//ns := RandomDigits(len([]rune(id)))
+	//ns := make([]byte, len(digits))
+	//for i := range ns {
+	//	d := digits[i]
+	//	switch {
+	//	case '0' <= d && d <= '9':
+	//		ns[i] = d - '0'
+	//	case d == ' ' || d == ',':
+	//		// ignore
+	//	default:
+	//		ns[i] = d
+	//	}
+	//}
+	//return Verify(id, ns)
+	return false
+}

+ 1 - 0
ipmatch/README.md

@@ -0,0 +1 @@
+IP黑白名单检测

+ 107 - 0
ipmatch/ipmatch.go

@@ -0,0 +1,107 @@
+package ipmatch
+
+import (
+	_ "embed"
+	"errors"
+	"github.com/RoaringBitmap/roaring"
+	"github.com/yl2chen/cidranger"
+	"net"
+	"strconv"
+	"strings"
+)
+
+type WhiteIp struct {
+	Rb     *roaring.Bitmap
+	Ranger cidranger.Ranger
+}
+
+type IpParameter struct {
+	Ip      string
+	IpType  int
+	IsWhite int
+}
+
+// ip2Uint32
+func ip2Uint32(rawIp string) (uint32, error) {
+	w := strings.Split(rawIp, ".")
+	if len(w) != 4 {
+		return 0, errors.New("error data type")
+	}
+	var ipInt uint32
+	for i, s := range w {
+		tmp, err := strconv.Atoi(s)
+		if err != nil {
+			return 0, err
+		}
+		ipInt += uint32(tmp << ((3 - i) * 8))
+	}
+	return ipInt, nil
+}
+
+// NewRb init
+func NewRb(ips []IpParameter) *WhiteIp {
+	newIp := new(WhiteIp)
+	rb := roaring.NewBitmap()
+	for _, ipData := range ips {
+		if len(ipData.Ip) < 8 {
+			continue
+		}
+		if ipData.IpType == 1 && isIP(ipData.Ip) { //精准ip添加
+			if ipUint, err := ip2Uint32(ipData.Ip); err == nil {
+				rb.Add(ipUint)
+			}
+		} else if ipData.IpType == 2 && isIPv4Segment(ipData.Ip) { //ip段添加
+			for _, v := range cidrToIPList(ipData.Ip) {
+				if ipUint, err1 := ip2Uint32(v); err1 == nil {
+					rb.Add(ipUint)
+				}
+			}
+		}
+	}
+	newIp.Rb = rb
+	return newIp
+}
+
+func cidrToIPList(cidr string) []string {
+	ip, ipNet, err := net.ParseCIDR(cidr)
+	if err != nil {
+		return nil
+	}
+
+	var ipList []string
+	for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) {
+		ipList = append(ipList, ip.String())
+	}
+
+	return ipList
+}
+
+func incIP(ip net.IP) {
+	for j := len(ip) - 1; j >= 0; j-- {
+		ip[j]++
+		if ip[j] > 0 {
+			break
+		}
+	}
+}
+
+// 判断字符串是否为有效的 IP 地址
+func isIP(s string) bool {
+	ip := net.ParseIP(s)
+	return ip != nil
+}
+
+// 判断字符串是否为有效的 IPv4 段
+func isIPv4Segment(s string) bool {
+	_, _, err := net.ParseCIDR(s)
+	return err == nil
+}
+
+// Match
+func (ip *WhiteIp) Match(rawIp string) bool {
+	if ipUint, err := ip2Uint32(rawIp); err != nil {
+		return false
+	} else {
+		return ip.Rb.Contains(ipUint)
+	}
+}

+ 230 - 0
log/log.go

@@ -0,0 +1,230 @@
+package log
+
+import (
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"gopkg.in/natefinch/lumberjack.v2"
+	"io"
+	"os"
+)
+
+var (
+	_logger *zap.Logger
+)
+
+const (
+	// FormatText format log text
+	FormatText = "text"
+	// FormatJSON format log json
+	FormatJSON = "json"
+)
+
+// type Level uint
+
+// 日志配置
+type logConfig struct {
+	LogPath    string
+	LogLevel   string
+	Compress   bool
+	MaxSize    int
+	MaxAge     int
+	MaxBackups int
+	Format     string
+}
+
+func getzapLevel(level string) zapcore.Level {
+	switch level {
+	case "debug":
+		return zap.DebugLevel
+	case "info":
+		return zap.InfoLevel
+	case "warn":
+		return zap.WarnLevel
+	case "error":
+		return zap.ErrorLevel
+	case "panic":
+		return zap.PanicLevel
+	case "fatal":
+		return zap.FatalLevel
+	default:
+		return zap.InfoLevel
+	}
+}
+
+func newLogWriter(logpath string, maxsize, maxbacks int, compress bool) io.Writer {
+	if logpath == "" || logpath == "-" {
+		return os.Stdout
+	}
+	return &lumberjack.Logger{
+		Filename:   logpath,
+		MaxSize:    maxsize,
+		MaxBackups: maxbacks,
+		Compress:   compress,
+	}
+}
+
+func newZapEncoder() zapcore.EncoderConfig {
+	encoderConfig := zapcore.EncoderConfig{
+		TimeKey:        "time",
+		LevelKey:       "level",
+		NameKey:        "logger",
+		CallerKey:      "line",
+		MessageKey:     "msg",
+		StacktraceKey:  "stacktrace",
+		LineEnding:     zapcore.DefaultLineEnding,
+		EncodeLevel:    zapcore.LowercaseLevelEncoder,  // 小写编码器
+		EncodeTime:     zapcore.ISO8601TimeEncoder,     // ISO8601 UTC 时间格式
+		EncodeDuration: zapcore.SecondsDurationEncoder, //
+		EncodeCaller:   zapcore.ShortCallerEncoder,     // 全路径编码器
+		EncodeName:     zapcore.FullNameEncoder,
+	}
+	return encoderConfig
+}
+func newLoggerCore(log *logConfig) zapcore.Core {
+	hook := newLogWriter(log.LogPath, log.MaxSize, log.MaxBackups, log.Compress)
+
+	encoderConfig := newZapEncoder()
+
+	atomLevel := zap.NewAtomicLevelAt(getzapLevel(log.LogLevel))
+
+	var encoder zapcore.Encoder
+	if log.Format == FormatJSON {
+		encoder = zapcore.NewJSONEncoder(encoderConfig)
+	} else {
+		encoder = zapcore.NewConsoleEncoder(encoderConfig)
+	}
+
+	core := zapcore.NewCore(
+		encoder,
+		zapcore.NewMultiWriteSyncer(zapcore.AddSync(hook)),
+		atomLevel,
+	)
+	return core
+}
+
+func newLoggerOptions() []zap.Option {
+	// 开启开发模式,堆栈跟踪
+	caller := zap.AddCaller()
+	callerskip := zap.AddCallerSkip(1)
+	// 开发者
+	development := zap.Development()
+	options := []zap.Option{
+		caller,
+		callerskip,
+		development,
+	}
+	return options
+}
+
+// Option function option
+type Option func(*logConfig)
+
+// Path set logpath
+// if is zero will print,or write file
+func Path(logpath string) Option {
+	return func(logcfg *logConfig) {
+		logcfg.LogPath = logpath
+	}
+}
+
+// Compress compress log
+func Compress(compress bool) Option {
+	return func(logcfg *logConfig) {
+		logcfg.Compress = compress
+	}
+}
+
+// Level set log level default info
+func Level(level string) Option {
+	return func(logcfg *logConfig) {
+		logcfg.LogLevel = level
+	}
+}
+
+// MaxSize Log Max Size
+func MaxSize(size int) Option {
+	return func(logcfg *logConfig) {
+		logcfg.MaxSize = size
+	}
+}
+
+// MaxAge log store day
+func MaxAge(age int) Option {
+	return func(logcfg *logConfig) {
+		logcfg.MaxAge = age
+	}
+}
+
+// MaxBackups total store log
+func MaxBackups(backup int) Option {
+	return func(logcfg *logConfig) {
+		logcfg.MaxBackups = backup
+	}
+}
+
+// Format log json or text
+func Format(format string) Option {
+	return func(logcfg *logConfig) {
+		if format == FormatJSON {
+			logcfg.Format = FormatJSON
+		} else {
+			logcfg.Format = FormatText
+		}
+
+	}
+}
+
+func defaultOption() *logConfig {
+	return &logConfig{
+		LogPath:    "",
+		MaxSize:    20,
+		Compress:   true,
+		MaxAge:     7,
+		MaxBackups: 7,
+		LogLevel:   "debug",
+		Format:     FormatText,
+	}
+}
+
+// InitLog conf
+func InitLog(opts ...Option) error {
+	logcfg := defaultOption()
+	for _, opt := range opts {
+		opt(logcfg)
+	}
+	core := newLoggerCore(logcfg)
+
+	zapopts := newLoggerOptions()
+	_logger = zap.New(core, zapopts...)
+	return nil
+}
+
+// Debug output log
+func Debug(msg string, fields ...zap.Field) {
+	_logger.Debug(msg, fields...)
+}
+
+// Info output log
+func Info(msg string, fields ...zap.Field) {
+	_logger.Info(msg, fields...)
+}
+
+// Warn output log
+func Warn(msg string, fields ...zap.Field) {
+	_logger.Warn(msg, fields...)
+}
+
+// Error output log
+func Error(msg string, fields ...zap.Field) {
+	_logger.Error(msg, fields...)
+}
+
+// Panic output panic
+func Panic(msg string, fields ...zap.Field) {
+	_logger.Panic(msg, fields...)
+}
+
+// Fatal output log
+func Fatal(msg string, fields ...zap.Field) {
+	_logger.Fatal(msg, fields...)
+}

+ 53 - 0
log/log_test.go

@@ -0,0 +1,53 @@
+package log
+
+import (
+	"errors"
+	"fmt"
+	"go.uber.org/zap"
+	"runtime/debug"
+
+	"io"
+	"os"
+	"testing"
+)
+
+func TestNewLogger(t *testing.T) {
+	logcfg := &logConfig{
+		MaxSize:    10,
+		Compress:   true,
+		LogPath:    "",
+		MaxAge:     0,
+		MaxBackups: 0,
+		LogLevel:   "info",
+	}
+	err := InitLog(
+		Path(logcfg.LogPath),
+		Level(logcfg.LogLevel),
+		Compress(logcfg.Compress),
+		MaxSize(logcfg.MaxSize),
+		MaxBackups(logcfg.MaxBackups),
+		MaxAge(logcfg.MaxAge),
+		Format("json"),
+	)
+	if err != nil {
+		fmt.Printf("InitLog failed: %v", err)
+		os.Exit(1)
+	}
+	defer func() {
+		if err := recover(); err != nil {
+			debug.PrintStack()
+		}
+	}()
+	Info("TestLog", zap.String("test", "eeyeyyeye"))
+	Debug("debug", zap.String("debug", "debug"))
+	Warn("warn", zap.String("warn", "warn"))
+	Error("error", zap.String("error", "error"))
+	Panic("panic", zap.String("panic", "panic"))
+	Fatal("fatal", zap.String("fatal", "fatal"))
+
+	err = io.EOF
+
+	err1 := fmt.Errorf("this is err: %w", err)
+	Info("111", zap.Error(err1))
+	fmt.Println(errors.Unwrap(err1), err1)
+}

+ 34 - 11
mail/gmail.go

@@ -48,12 +48,11 @@ func getDialer(flag bool, auth *GmailAuth, to string) *gomail.Dialer {
 	return <-auth.PoolChan
 }
 
-//发送普通
+// 发送普通
 func GSendMail(from, to, cc, bcc, subject, body, fname, rename string, auth *GmailAuth) bool {
 	m := gomail.NewMessage()
-	m.SetAddressHeader("From", auth.User, from) // 发件人
-	m.SetHeader("To",
-		m.FormatAddress(to, "收件人")) // 收件人
+	m.SetAddressHeader("From", auth.User, from)  // 发件人
+	m.SetHeader("To", strings.Split(to, ",")...) // 收件人
 	if cc != "" {
 		m.SetHeader("Cc", m.FormatAddress(cc, "收件人")) //抄送
 	}
@@ -74,7 +73,32 @@ func GSendMail(from, to, cc, bcc, subject, body, fname, rename string, auth *Gma
 	return gSend(reTry, auth, m, to)
 }
 
-//如果附件是byte,用这个
+// 电销 多人
+func GSendMail_dx(from, to, cc, bcc, subject, body, fname, rename string, auth *GmailAuth) bool {
+	m := gomail.NewMessage()
+	m.SetAddressHeader("From", auth.User, from)  // 发件人
+	m.SetHeader("To", strings.Split(to, ",")...) // 收件人
+	if cc != "" {
+		m.SetHeader("Cc", strings.Split(cc, ",")...) //抄送
+	}
+	if bcc != "" {
+		m.SetHeader("Bcc", m.FormatAddress(bcc, "收件人")) // 暗送
+	}
+	m.SetHeader("Subject", subject) // 主题
+	m.SetBody("text/html", body)    // 正文
+	if fname != "" {
+		h := map[string][]string{"Content-Type": {"text/plain; charset=UTF-8"}}
+		m.Attach(fname, gomail.Rename(rename), gomail.SetHeader(h)) //添加附件
+		//m.Attach(fname) //添加附件
+	}
+	reTry := auth.ReTry
+	if reTry == 0 {
+		reTry = 3
+	}
+	return gSend(reTry, auth, m, to)
+}
+
+// 如果附件是byte,用这个
 func GSendMail_B(from, to, cc, bcc, subject, body, fname string, fb []byte, auth *GmailAuth) bool {
 	m := gomail.NewMessage()
 	m.SetAddressHeader("From", auth.User, from) // 发件人
@@ -100,8 +124,8 @@ func GSendMail_B(from, to, cc, bcc, subject, body, fname string, fb []byte, auth
 	return gSend(reTry, auth, m, to)
 }
 
-//如果附件是byte,用这个,这个是为企业级服务修改的
-//20191206 @ren  to用|分隔的是抄送用,分隔的是并列
+// 如果附件是byte,用这个,这个是为企业级服务修改的
+// 20191206 @ren  to用|分隔的是抄送用,分隔的是并列
 func GSendMail_Bq(from, to, cc, bcc, subject, body, fname string, fb []byte, auth *GmailAuth) bool {
 	m := gomail.NewMessage()
 	m.SetAddressHeader("From", auth.User, from) // 发件人
@@ -146,8 +170,8 @@ func GSendMail_Bq(from, to, cc, bcc, subject, body, fname string, fb []byte, aut
 	return gSend(reTry, auth, m, to)
 }
 
-//发送普通这个是为企业级服务修改的
-//20191206 @ren  to用|分隔的是抄送,分隔的是并列
+// 发送普通这个是为企业级服务修改的
+// 20191206 @ren  to用|分隔的是抄送,分隔的是并列
 func GSendMail_q(from, to, cc, bcc, subject, body, fname, rename string, auth *GmailAuth) bool {
 	m := gomail.NewMessage()
 	m.SetAddressHeader("From", auth.User, from) // 发件人
@@ -192,7 +216,6 @@ func GSendMail_q(from, to, cc, bcc, subject, body, fname, rename string, auth *G
 	return gSend(reTry, auth, m, to)
 }
 
-//
 func gSend(retry int, auth *GmailAuth, m *gomail.Message, to string) bool {
 	defer common.Catch()
 	dialer := getDialer(false, auth, to)
@@ -215,7 +238,7 @@ func gSend(retry int, auth *GmailAuth, m *gomail.Message, to string) bool {
 	return status
 }
 
-//先取模后轮询获取一个mail实例
+// 先取模后轮询获取一个mail实例
 func PollingMail(email string, array []*GmailAuth, f func(g *GmailAuth) bool) bool {
 	if len(array) == 0 {
 		return false

+ 4 - 0
mongodb/mongodb.go

@@ -348,6 +348,7 @@ type MongodbSim struct {
 	UserName string
 	Password string
 	ReplSet  string
+	Direct   bool
 }
 
 func (m *MongodbSim) GetMgoConn() *MgoSess {
@@ -371,6 +372,9 @@ func (m *MongodbSim) Destroy() {
 
 func (m *MongodbSim) InitPool() {
 	opts := options.Client()
+	if m.Direct {
+		opts.SetDirect(true)
+	}
 	registry := bson.NewRegistryBuilder().RegisterTypeMapEntry(bson.TypeArray, reflect.TypeOf([]interface{}{})).Build()
 	opts.SetRegistry(registry)
 	opts.SetConnectTimeout(3 * time.Second)

+ 94 - 46
mysql/mysql.go

@@ -12,6 +12,10 @@ import (
 	_ "github.com/go-sql-driver/mysql"
 )
 
+const (
+	CLICKHOUSE = "clickhouse"
+)
+
 type Mysql struct {
 	Address      string  //数据库地址:端口
 	UserName     string  //用户名
@@ -20,28 +24,40 @@ type Mysql struct {
 	DB           *sql.DB `json:",optional"` //数据库连接池对象
 	MaxOpenConns int     //用于设置最大打开的连接数,默认值为0表示不限制。
 	MaxIdleConns int     //用于设置闲置的连接数。
+	driverName   string
 }
 
-func (m *Mysql) Init() {
-	if m.MaxOpenConns <= 0 {
-		m.MaxOpenConns = 30
+//
+func NewInit(driverName, dataSourceName string, maxOpenConns, maxIdleConns int) *Mysql {
+	if maxOpenConns <= 0 {
+		maxOpenConns = 30
 	}
-	if m.MaxIdleConns <= 0 {
-		m.MaxIdleConns = 6
+	if maxIdleConns <= 0 {
+		maxIdleConns = 6
 	}
-	var err error
-	m.DB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", m.UserName, m.PassWord, m.Address, m.DBName))
+	db, err := sql.Open(driverName, dataSourceName)
 	if err != nil {
-		log.Println(err)
-		return
+		log.Println("open error", err)
+		return nil
 	}
-	m.DB.SetMaxOpenConns(m.MaxOpenConns)
-	m.DB.SetMaxIdleConns(m.MaxIdleConns)
-	m.DB.SetConnMaxLifetime(14400 * time.Second)
-	err = m.DB.Ping()
-	if err != nil {
-		log.Println(err)
+	db.SetMaxOpenConns(maxOpenConns)
+	db.SetMaxIdleConns(maxIdleConns)
+	db.SetConnMaxLifetime(14400 * time.Second)
+	if err := db.Ping(); err != nil {
+		log.Println("ping error", err)
+		return nil
 	}
+	return &Mysql{DB: db, driverName: driverName}
+}
+
+//
+func (m *Mysql) Init() {
+	m.driverName = "mysql"
+	nm := NewInit(m.driverName, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", m.UserName, m.PassWord, m.Address, m.DBName), m.MaxOpenConns, m.MaxIdleConns)
+	if nm == nil {
+		return
+	}
+	m.DB = nm.DB
 }
 
 //新增
@@ -65,7 +81,7 @@ func (m *Mysql) InsertByTx(tx *sql.Tx, tableName string, data map[string]interfa
 		placeholders = append(placeholders, "?")
 	}
 	q := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, strings.Join(fields, ","), strings.Join(placeholders, ","))
-	log.Println("mysql", q, values)
+	log.Println(q, values)
 	return m.InsertBySqlByTx(tx, q, values...)
 }
 
@@ -80,6 +96,9 @@ func (m *Mysql) InsertBySqlByTx(tx *sql.Tx, q string, args ...interface{}) int64
 	if result == nil {
 		return -1
 	}
+	if m.driverName == CLICKHOUSE {
+		return 0
+	}
 	id, err := result.LastInsertId()
 	if err != nil {
 		log.Println(err)
@@ -133,6 +152,9 @@ func (m *Mysql) insertOrReplaceBatchByTx(tx *sql.Tx, tp string, afterInsert, tab
 	if result == nil {
 		return -1, -1
 	}
+	if m.driverName == CLICKHOUSE {
+		return 0, 0
+	}
 	v1, e1 := result.RowsAffected()
 	if e1 != nil {
 		log.Println(e1)
@@ -153,19 +175,24 @@ func (m *Mysql) ExecBySql(q string, args ...interface{}) (sql.Result, error) {
 
 //sql语句执行,带有事务
 func (m *Mysql) ExecBySqlByTx(tx *sql.Tx, q string, args ...interface{}) (sql.Result, error) {
-	var stmtIns *sql.Stmt
+	var result sql.Result
 	var err error
-	if tx == nil {
-		stmtIns, err = m.DB.Prepare(q)
+	if m.driverName == CLICKHOUSE {
+		result, err = m.DB.Exec(q, args...)
 	} else {
-		stmtIns, err = tx.Prepare(q)
-	}
-	if err != nil {
-		log.Println(err)
-		return nil, err
+		var stmtIns *sql.Stmt
+		if tx == nil {
+			stmtIns, err = m.DB.Prepare(q)
+		} else {
+			stmtIns, err = tx.Prepare(q)
+		}
+		if err != nil {
+			log.Println(err)
+			return nil, err
+		}
+		defer stmtIns.Close()
+		result, err = stmtIns.Exec(args...)
 	}
-	defer stmtIns.Close()
-	result, err := stmtIns.Exec(args...)
 	if err != nil {
 		log.Println(args, err)
 		return nil, err
@@ -259,19 +286,24 @@ func (m *Mysql) SelectBySqlByTx(tx *sql.Tx, q string, args ...interface{}) *[]ma
 	return m.Select(0, nil, tx, q, args...)
 }
 func (m *Mysql) Select(bath int, f func(l *[]map[string]interface{}) bool, tx *sql.Tx, q string, args ...interface{}) *[]map[string]interface{} {
-	var stmtOut *sql.Stmt
+	var rows *sql.Rows
 	var err error
-	if tx == nil {
-		stmtOut, err = m.DB.Prepare(q)
+	if m.driverName == CLICKHOUSE {
+		rows, err = m.DB.Query(q, args...)
 	} else {
-		stmtOut, err = tx.Prepare(q)
-	}
-	if err != nil {
-		log.Println(err)
-		return nil
+		var stmtOut *sql.Stmt
+		if tx == nil {
+			stmtOut, err = m.DB.Prepare(q)
+		} else {
+			stmtOut, err = tx.Prepare(q)
+		}
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		defer stmtOut.Close()
+		rows, err = stmtOut.Query(args...)
 	}
-	defer stmtOut.Close()
-	rows, err := stmtOut.Query(args...)
 	if err != nil {
 		log.Println(err)
 		return nil
@@ -307,6 +339,7 @@ func (m *Mysql) Select(bath int, f func(l *[]map[string]interface{}) bool, tx *s
 		list = append(list, ret)
 		if bath > 0 && len(list) == bath {
 			if !f(&list) {
+				list = []map[string]interface{}{}
 				break
 			}
 			list = []map[string]interface{}{}
@@ -414,11 +447,13 @@ func (m *Mysql) UpdateOrDeleteBySql(q string, args ...interface{}) int64 {
 
 //带事务的修改或删除
 func (m *Mysql) UpdateOrDeleteBySqlByTx(tx *sql.Tx, q string, args ...interface{}) int64 {
-	result, err := m.ExecBySqlByTx(tx, q, args...)
-	if err != nil {
-		log.Println(err)
+	result, _ := m.ExecBySqlByTx(tx, q, args...)
+	if result == nil {
 		return -1
 	}
+	if m.driverName == CLICKHOUSE {
+		return 0
+	}
 	count, err := result.RowsAffected()
 	if err != nil {
 		log.Println(err)
@@ -479,14 +514,27 @@ func (m *Mysql) Count(tableName string, query map[string]interface{}) int64 {
 	return m.CountBySql(q, values...)
 }
 func (m *Mysql) CountBySql(q string, args ...interface{}) int64 {
-	stmtIns, err := m.DB.Prepare(q)
-	if err != nil {
-		log.Println(err)
-		return -1
+	return m.CountBySqlByTx(nil, q, args...)
+}
+func (m *Mysql) CountBySqlByTx(tx *sql.Tx, q string, args ...interface{}) int64 {
+	var rows *sql.Rows
+	var err error
+	if m.driverName == CLICKHOUSE {
+		rows, err = m.DB.Query(q, args...)
+	} else {
+		var stmtOut *sql.Stmt
+		if tx == nil {
+			stmtOut, err = m.DB.Prepare(q)
+		} else {
+			stmtOut, err = tx.Prepare(q)
+		}
+		if err != nil {
+			log.Println(err)
+			return -1
+		}
+		defer stmtOut.Close()
+		rows, err = stmtOut.Query(args...)
 	}
-	defer stmtIns.Close()
-
-	rows, err := stmtIns.Query(args...)
 	if err != nil {
 		log.Println(err)
 		return -1

+ 35 - 0
mysql/mysql_test.go

@@ -0,0 +1,35 @@
+package mysql
+
+import (
+	"log"
+	"testing"
+)
+
+func TestMysqlSelect(t *testing.T) {
+	m := &Mysql{
+		Address:      "192.168.3.217:4000",
+		UserName:     "root",
+		PassWord:     "=PDT49#80Z!RVv52_z",
+		DBName:       "jianyu",
+		MaxOpenConns: 2, //用于设置最大打开的连接数,默认值为0表示不限制。
+		MaxIdleConns: 2, //用于设置闲置的连接数。
+	}
+	m.Init()
+	list := m.SelectBySql(`select * from entniche_info limit ?`, 1)
+	log.Println("-----", list)
+}
+
+//
+func TestClickHouseSelect(t *testing.T) {
+	m := NewInit("clickhouse", "clickhouse://jytop:pwdTopJy123@192.168.3.207:19000/information?dial_timeout=2000ms&max_execution_time=60s", 1, 1)
+	m.SelectByBath(1, func(l *[]map[string]interface{}) bool {
+		log.Println(l)
+		return true
+	}, `select * from information where createtime>=1708673243 and createtime<1708673245`)
+	//}, `select id from information limit ?`, 1)
+	return
+	list := m.SelectBySql(`select id from information limit ?`, 1)
+	c := m.CountBySql(`select count(1) as c from information`)
+	log.Println("-----", list, c)
+	log.Println(m.InsertBatch("wcj.test", []string{"name"}, []interface{}{"456"}))
+}

BIN
procimg/main.jpg


BIN
procimg/new.jpg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 12 - 0
procimg/procimg.go


+ 26 - 0
procimg/procimg_test.go

@@ -0,0 +1,26 @@
+package procimg
+
+import (
+	"image/jpeg"
+	"log"
+	"os"
+	"testing"
+)
+
+func TestProcImg(t *testing.T) {
+	//图片,网上随便找了一张
+	img_file, err := os.Open("main.jpg")
+	if err != nil {
+		log.Fatalln(err)
+	}
+	defer img_file.Close()
+	img, err := jpeg.Decode(img_file)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	m := WaterMark(img, BgImg, 0.5)
+	imgw, err := os.Create("./new.jpg")
+	jpeg.Encode(imgw, m, &jpeg.Options{75})
+	defer imgw.Close()
+	log.Println("添加水印图片结束请查看")
+}

+ 24 - 0
rpc/rpccall.go

@@ -70,3 +70,27 @@ func AppPush(address string, m map[string]interface{}) bool {
 	}
 	return repl == "y"
 }
+
+/*企业级服务、企业数据流量包导数据、历史数据统一数据去重
+ *@param address rpc服务地址
+ *@param m 示例:map[string]interface{}{"appid": "","entid": 1,"ids": []string{"6466e5d78aea8786d104943c", "6465933e759cdf5aef2ef69c"}}
+ * appid、entid 二选一,判重的时候会优先使用appid
+ * ids 需要去重的标讯id集合
+ *@result map[string]bool{"6466e5d78aea8786d104943c":true} 去重后的标讯id集合
+ */
+func NoRepeatData(address string, m map[string]interface{}) (map[string]bool, error) {
+	defer util.Catch()
+	var repl = make(map[string]bool)
+	client, err := rpc.DialHTTP("tcp", address)
+	if err != nil {
+		log.Println("RemoveRepeat.NoRepeat rpc call error", err.Error())
+		return repl, err
+	}
+	defer client.Close()
+	err = client.Call("RemoveRepeat.NoRepeat", m, &repl)
+	if err != nil {
+		log.Println("RemoveRepeat.NoRepeat rpc call error", err.Error())
+		return repl, err
+	}
+	return repl, nil
+}

+ 26 - 0
rpc/task.go

@@ -0,0 +1,26 @@
+package rpc
+
+//获取任务信息
+type TaskData struct {
+	IsNew bool
+}
+
+//任务信息结果
+type TaskDataResp struct {
+	Error_code int64         `json:"error_code"`
+	Error_msg  string        `json:"error_msg"`
+	Data       []*TaskStruct `json:"data"`
+}
+
+//任务信息
+type TaskStruct struct {
+	Name        string `json:"name"`
+	Desc        string `json:"desc"`
+	PcHref      string `json:"pcHref"`
+	AppHref     string `json:"appHref"`
+	WxHref      string `json:"wxHref"`
+	Point       int    `json:"point"`
+	Icon        string `json:"icon"`
+	Type        string `json:"type"`
+	Distinguish int    `json:"distinguish"` // 0不用区分身份 1区分身份
+}

+ 10 - 4
rpc/weixin.go

@@ -48,8 +48,14 @@ type TmplItem struct {
 }
 
 type WxTmplMsg struct {
-	OpenId   string //用户id
-	Url      string
-	TplId    string
-	TmplData map[string]*TmplItem
+	GzhCode     string //公众号代码,碎片化的公众号需要传
+	OpenId      string //用户id
+	Url         string
+	TplId       string
+	TmplData    map[string]*TmplItem
+	MiniProgram *MiniProgram
+}
+type MiniProgram struct {
+	AppId    string `json:"appid,omitempty"`
+	PagePath string `json:"pagepath,omitempty"`
 }

+ 2 - 1
sms/sms.go

@@ -15,7 +15,7 @@ import (
  *@Param mobile 手机号
  *@Param params 消息模板中变量
  */
-func SendSms(address, id, phones string, params ...string) {
+func SendSms(ip, address, id, phones string, params ...string) {
 	go func() {
 		defer Catch()
 		var repl string
@@ -29,6 +29,7 @@ func SendSms(address, id, phones string, params ...string) {
 			"id":     id,
 			"phones": phones,
 			"params": params,
+			"ip":     ip,
 		})
 		err = client.Call("Sms.Execute", b, &repl)
 		if err != nil {

+ 3 - 2
sms/sms_test.go

@@ -1,11 +1,12 @@
 package sms
 
 import (
+	"fmt"
 	"testing"
 	"time"
 )
 
 func TestSendSms(t *testing.T) {
-	SendSms("192.168.3.11:932", "02", "15037870765", "用户")
-	time.Sleep(2 * time.Minute)
+	SendSms("157.35.35.35", "127.0.0.1:932", "01", "15225277818", fmt.Sprintf("用户00%d", 1))
+	time.Sleep(10 * time.Second)
 }

+ 15 - 3
sort/sort.go

@@ -86,11 +86,23 @@ func (s *ComSortList) Less(i, j int) bool {
 			continue
 		}
 		if v.Type == "string" {
-			return ObjToString(i_v) > ObjToString(j_v) && v.Order < 0
+			if v.Order < 0 {
+				return ObjToString(i_v) > ObjToString(j_v)
+			} else {
+				return ObjToString(i_v) < ObjToString(j_v)
+			}
 		} else if v.Type == "float" {
-			return Float64All(i_v) > Float64All(j_v) && v.Order < 0
+			if v.Order < 0 {
+				return Float64All(i_v) > Float64All(j_v)
+			} else {
+				return Float64All(i_v) < Float64All(j_v)
+			}
 		} else {
-			return Int64All(i_v) > Int64All(j_v) && v.Order < 0
+			if v.Order < 0 {
+				return Int64All(i_v) > Int64All(j_v)
+			} else {
+				return Int64All(i_v) < Int64All(j_v)
+			}
 		}
 	}
 	return false

Vissa filer visades inte eftersom för många filer har ändrats