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  }