github.com/songzhibin97/gkit@v1.2.13/page_token/token.go (about) 1 package page_token 2 3 import ( 4 "encoding/base64" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/songzhibin97/gkit/options" 12 13 "github.com/songzhibin97/gkit/encrypt/aes" 14 ) 15 16 var ( 17 ErrInvalidToken = errors.New("the field `page_token` is invalid") 18 ErrOverdueToken = errors.New("the field `page_token` is overdue") 19 ErrOverMaxPageSizeToken = errors.New("the field `page_token` is over max page size") 20 ErrInvalidPageSize = errors.New("the page size provided must not be negative") 21 ) 22 23 const ( 24 defaultMaxIndex = 0 25 defaultMaxElements = 0 26 defaultSalt = "gkit" 27 layout = "2006-01-02 15-04-05" 28 ) 29 30 type token struct { 31 salt string // Special identification 32 resourceIdentification string // Resource identification 33 timeLimitation time.Duration // Time limitation 34 maxIndex int // Maximum index 35 maxElements int // Maximum number of elements 36 } 37 38 func (t *token) ForIndex(i int) string { 39 v := aes.Encrypt(fmt.Sprintf("%s%s:%d", t.resourceIdentification, time.Now().Format(layout), i), t.salt) 40 return base64.StdEncoding.EncodeToString( 41 []byte(v)) 42 } 43 44 func (t *token) GetIndex(s string) (int, error) { 45 if s == "" { 46 return 0, nil 47 } 48 bs, err := base64.StdEncoding.DecodeString(s) 49 if err != nil { 50 return -1, ErrInvalidToken 51 } 52 decrypted := aes.Decrypt(string(bs), t.salt) 53 if decrypted == "" { 54 return -1, ErrInvalidToken 55 } 56 parseToken := strings.Split(strings.TrimPrefix(string(decrypted), t.resourceIdentification), ":") 57 if len(parseToken) != 2 { 58 return -1, ErrInvalidToken 59 } 60 if t.timeLimitation != 0 { 61 generateTime, err := time.Parse(layout, parseToken[0]) 62 if err != nil { 63 return -1, ErrInvalidToken 64 } 65 if generateTime.Add(t.timeLimitation).After(time.Now()) { 66 return -1, ErrOverdueToken 67 } 68 } 69 i, err := strconv.Atoi(parseToken[1]) 70 if err != nil { 71 return -1, ErrInvalidToken 72 } 73 if t.maxIndex != defaultMaxIndex && i > t.maxIndex { 74 return -1, ErrOverMaxPageSizeToken 75 } 76 return i, nil 77 } 78 79 func (t *token) ProcessPageTokens(numElements int, pageSize int, pageToken string) (start, end int, nextToken string, err error) { 80 if pageSize < 0 { 81 return 0, 0, "", ErrInvalidPageSize 82 } 83 84 if t.maxElements != defaultMaxElements && numElements > t.maxElements { 85 numElements = t.maxElements 86 } 87 88 if pageToken != "" { 89 index, err := t.GetIndex(pageToken) 90 if err != nil { 91 return 0, 0, "", err 92 } 93 94 token64 := index 95 if token64 < 0 || token64 >= numElements { 96 return 0, 0, "", ErrInvalidToken 97 } 98 start = token64 99 } 100 101 if pageSize == 0 { 102 pageSize = numElements 103 } 104 end = min(start+pageSize, numElements) 105 106 if end < numElements { 107 nextToken = t.ForIndex(int(end)) 108 } 109 110 return start, end, nextToken, nil 111 } 112 113 func min(a, b int) int { 114 if a > b { 115 return b 116 } 117 return a 118 } 119 120 func NewTokenGenerate(resourceIdentification string, options ...options.Option) PageToken { 121 t := &token{ 122 maxIndex: defaultMaxIndex, 123 maxElements: defaultMaxElements, 124 timeLimitation: 0, 125 salt: aes.PadKey(defaultSalt), 126 resourceIdentification: resourceIdentification, 127 } 128 for _, option := range options { 129 option(t) 130 } 131 return t 132 }