github.com/TeaOSLab/EdgeNode@v1.3.8/internal/iplibrary/ip_list_kv.go (about)

     1  // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
     2  
     3  package iplibrary
     4  
     5  import (
     6  	"encoding/binary"
     7  	"errors"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     9  	"github.com/TeaOSLab/EdgeNode/internal/events"
    10  	"github.com/TeaOSLab/EdgeNode/internal/goman"
    11  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    12  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
    13  	"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
    14  	"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
    15  	"testing"
    16  	"time"
    17  )
    18  
    19  type KVIPList struct {
    20  	ipTable       *kvstore.Table[*pb.IPItem]
    21  	versionsTable *kvstore.Table[int64]
    22  
    23  	encoder *IPItemEncoder[*pb.IPItem]
    24  
    25  	cleanTicker *time.Ticker
    26  
    27  	isClosed bool
    28  
    29  	offsetItemKey string
    30  }
    31  
    32  func NewKVIPList() (*KVIPList, error) {
    33  	var db = &KVIPList{
    34  		cleanTicker: time.NewTicker(24 * time.Hour),
    35  		encoder:     &IPItemEncoder[*pb.IPItem]{},
    36  	}
    37  	err := db.init()
    38  	return db, err
    39  }
    40  
    41  func (this *KVIPList) init() error {
    42  	store, storeErr := kvstore.DefaultStore()
    43  	if storeErr != nil {
    44  		return storeErr
    45  	}
    46  	db, dbErr := store.NewDB("ip_list")
    47  	if dbErr != nil {
    48  		return dbErr
    49  	}
    50  
    51  	{
    52  		table, err := kvstore.NewTable[*pb.IPItem]("ip_items", this.encoder)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		this.ipTable = table
    57  
    58  		err = table.AddFields("expiresAt")
    59  		if err != nil {
    60  			return err
    61  		}
    62  
    63  		db.AddTable(table)
    64  	}
    65  
    66  	{
    67  		table, err := kvstore.NewTable[int64]("versions", kvstore.NewIntValueEncoder[int64]())
    68  		if err != nil {
    69  			return err
    70  		}
    71  		this.versionsTable = table
    72  		db.AddTable(table)
    73  	}
    74  
    75  	goman.New(func() {
    76  		events.OnClose(func() {
    77  			_ = this.Close()
    78  			this.cleanTicker.Stop()
    79  		})
    80  
    81  		idles.RunTicker(this.cleanTicker, func() {
    82  			if this.isClosed {
    83  				return
    84  			}
    85  			deleteErr := this.DeleteExpiredItems()
    86  			if deleteErr != nil {
    87  				remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
    88  			}
    89  		})
    90  	})
    91  
    92  	return nil
    93  }
    94  
    95  // Name 数据库名称代号
    96  func (this *KVIPList) Name() string {
    97  	return "kvstore"
    98  }
    99  
   100  // DeleteExpiredItems 删除过期的条目
   101  func (this *KVIPList) DeleteExpiredItems() error {
   102  	if this.isClosed {
   103  		return nil
   104  	}
   105  
   106  	for {
   107  		var found bool
   108  		var currentTime = fasttime.Now().Unix()
   109  		err := this.ipTable.
   110  			Query().
   111  			FieldAsc("expiresAt").
   112  			ForUpdate().
   113  			Limit(1000).
   114  			FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
   115  				if !item.Value.IsDeleted && item.Value.ExpiredAt == 0 { // never expires
   116  					return kvstore.Skip()
   117  				}
   118  				if item.Value.ExpiredAt < currentTime-7*86400 /** keep for 7 days **/ {
   119  					err = tx.Delete(item.Key)
   120  					if err != nil {
   121  						return false, err
   122  					}
   123  					found = true
   124  					return true, nil
   125  				}
   126  
   127  				found = false
   128  				return false, nil
   129  			})
   130  		if err != nil {
   131  			return err
   132  		}
   133  		if !found {
   134  			break
   135  		}
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (this *KVIPList) AddItem(item *pb.IPItem) error {
   142  	if this.isClosed {
   143  		return nil
   144  	}
   145  
   146  	// 先删除
   147  	var key = this.encoder.EncodeKey(item)
   148  	err := this.ipTable.Delete(key)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	// 如果是删除,则不再创建新记录
   154  	if item.IsDeleted {
   155  		return this.UpdateMaxVersion(item.Version)
   156  	}
   157  
   158  	err = this.ipTable.Set(key, item)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	return this.UpdateMaxVersion(item.Version)
   164  }
   165  
   166  func (this *KVIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNextLoop bool, err error) {
   167  	if this.isClosed {
   168  		return
   169  	}
   170  
   171  	err = this.ipTable.
   172  		Query().
   173  		Offset(this.offsetItemKey).
   174  		Limit(int(size)).
   175  		FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
   176  			this.offsetItemKey = item.Key
   177  			goNextLoop = true
   178  
   179  			if !item.Value.IsDeleted {
   180  				items = append(items, item.Value)
   181  			}
   182  			return true, nil
   183  		})
   184  	return
   185  }
   186  
   187  // ReadMaxVersion 读取当前最大版本号
   188  func (this *KVIPList) ReadMaxVersion() (int64, error) {
   189  	if this.isClosed {
   190  		return 0, errors.New("database has been closed")
   191  	}
   192  
   193  	version, err := this.versionsTable.Get("version")
   194  	if err != nil {
   195  		if kvstore.IsNotFound(err) {
   196  			return 0, nil
   197  		}
   198  		return 0, err
   199  	}
   200  	return version, nil
   201  }
   202  
   203  // UpdateMaxVersion 修改版本号
   204  func (this *KVIPList) UpdateMaxVersion(version int64) error {
   205  	if this.isClosed {
   206  		return nil
   207  	}
   208  
   209  	return this.versionsTable.Set("version", version)
   210  }
   211  
   212  func (this *KVIPList) TestInspect(t *testing.T) error {
   213  	return this.ipTable.
   214  		Query().
   215  		FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
   216  			if len(item.Key) != 8 {
   217  				return false, errors.New("invalid key '" + item.Key + "'")
   218  			}
   219  
   220  			t.Log(binary.BigEndian.Uint64([]byte(item.Key)), "=>", item.Value)
   221  			return true, nil
   222  		})
   223  }
   224  
   225  // Flush to disk
   226  func (this *KVIPList) Flush() error {
   227  	return this.ipTable.DB().Store().Flush()
   228  }
   229  
   230  func (this *KVIPList) Close() error {
   231  	this.isClosed = true
   232  	return nil
   233  }