mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	[WAF]规则支持导入导出
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -6,6 +6,7 @@ replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
 | 
						github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
 | 
				
			||||||
 | 
						github.com/cespare/xxhash v1.1.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.5.0
 | 
						github.com/go-sql-driver/mysql v1.5.0
 | 
				
			||||||
	github.com/go-yaml/yaml v2.1.0+incompatible
 | 
						github.com/go-yaml/yaml v2.1.0+incompatible
 | 
				
			||||||
	github.com/iwind/TeaGo v0.0.0-20201120063500-ee2d7090f4bc
 | 
						github.com/iwind/TeaGo v0.0.0-20201120063500-ee2d7090f4bc
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@@ -3,8 +3,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
 | 
				
			|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
					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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
				
			||||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
 | 
					github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
 | 
				
			||||||
 | 
					github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
				
			||||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
 | 
					github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
 | 
				
			||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
					github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
				
			||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
					github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
				
			||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
					github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
				
			||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 | 
					github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 | 
				
			||||||
@@ -91,6 +94,7 @@ github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi
 | 
				
			|||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 | 
					github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 | 
				
			||||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
 | 
					github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
 | 
				
			||||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
 | 
					github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
 | 
				
			||||||
 | 
					github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 | 
					github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										151
									
								
								internal/ttlcache/cache.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								internal/ttlcache/cache.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/utils"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var DefaultCache = NewCache()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TTL缓存
 | 
				
			||||||
 | 
					// 最大的缓存时间为30 * 86400
 | 
				
			||||||
 | 
					// Piece数据结构:
 | 
				
			||||||
 | 
					//      Piece1          |  Piece2 | Piece3 | ...
 | 
				
			||||||
 | 
					//  [ Item1, Item2, ... |   ...
 | 
				
			||||||
 | 
					// KeyMap列表数据结构
 | 
				
			||||||
 | 
					// { timestamp1 => [key1, key2, ...] }, ...
 | 
				
			||||||
 | 
					type Cache struct {
 | 
				
			||||||
 | 
						isDestroyed bool
 | 
				
			||||||
 | 
						pieces      []*Piece
 | 
				
			||||||
 | 
						countPieces uint64
 | 
				
			||||||
 | 
						maxItems    int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gcPieceIndex int
 | 
				
			||||||
 | 
						ticker       *utils.Ticker
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewCache(opt ...OptionInterface) *Cache {
 | 
				
			||||||
 | 
						countPieces := 128
 | 
				
			||||||
 | 
						maxItems := 1_000_000
 | 
				
			||||||
 | 
						for _, option := range opt {
 | 
				
			||||||
 | 
							if option == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch o := option.(type) {
 | 
				
			||||||
 | 
							case *PiecesOption:
 | 
				
			||||||
 | 
								if o.Count > 0 {
 | 
				
			||||||
 | 
									countPieces = o.Count
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case *MaxItemsOption:
 | 
				
			||||||
 | 
								if o.Count > 0 {
 | 
				
			||||||
 | 
									maxItems = o.Count
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cache := &Cache{
 | 
				
			||||||
 | 
							countPieces: uint64(countPieces),
 | 
				
			||||||
 | 
							maxItems:    maxItems,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < countPieces; i++ {
 | 
				
			||||||
 | 
							cache.pieces = append(cache.pieces, NewPiece(maxItems/countPieces))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// start timer
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							cache.ticker = utils.NewTicker(5 * time.Second)
 | 
				
			||||||
 | 
							for cache.ticker.Next() {
 | 
				
			||||||
 | 
								cache.GC()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) Write(key string, value interface{}, expiredAt int64) {
 | 
				
			||||||
 | 
						if this.isDestroyed {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						currentTimestamp := time.Now().Unix()
 | 
				
			||||||
 | 
						if expiredAt <= currentTimestamp {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxExpiredAt := currentTimestamp + 30*86400
 | 
				
			||||||
 | 
						if expiredAt > maxExpiredAt {
 | 
				
			||||||
 | 
							expiredAt = maxExpiredAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						uint64Key := HashKey([]byte(key))
 | 
				
			||||||
 | 
						pieceIndex := uint64Key % this.countPieces
 | 
				
			||||||
 | 
						this.pieces[pieceIndex].Add(uint64Key, &Item{
 | 
				
			||||||
 | 
							Value:     value,
 | 
				
			||||||
 | 
							expiredAt: expiredAt,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) IncreaseInt64(key string, delta int64, expiredAt int64) int64 {
 | 
				
			||||||
 | 
						if this.isDestroyed {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						currentTimestamp := time.Now().Unix()
 | 
				
			||||||
 | 
						if expiredAt <= currentTimestamp {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxExpiredAt := currentTimestamp + 30*86400
 | 
				
			||||||
 | 
						if expiredAt > maxExpiredAt {
 | 
				
			||||||
 | 
							expiredAt = maxExpiredAt
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						uint64Key := HashKey([]byte(key))
 | 
				
			||||||
 | 
						pieceIndex := uint64Key % this.countPieces
 | 
				
			||||||
 | 
						return this.pieces[pieceIndex].IncreaseInt64(uint64Key, delta, expiredAt)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) Read(key string) (item *Item) {
 | 
				
			||||||
 | 
						uint64Key := HashKey([]byte(key))
 | 
				
			||||||
 | 
						return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) readIntKey(key uint64) (value *Item) {
 | 
				
			||||||
 | 
						return this.pieces[key%this.countPieces].Read(key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) Delete(key string) {
 | 
				
			||||||
 | 
						uint64Key := HashKey([]byte(key))
 | 
				
			||||||
 | 
						this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) deleteIntKey(key uint64) {
 | 
				
			||||||
 | 
						this.pieces[key%this.countPieces].Delete(key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) Count() (count int) {
 | 
				
			||||||
 | 
						for _, piece := range this.pieces {
 | 
				
			||||||
 | 
							count += piece.Count()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) GC() {
 | 
				
			||||||
 | 
						this.pieces[this.gcPieceIndex].GC()
 | 
				
			||||||
 | 
						newIndex := this.gcPieceIndex + 1
 | 
				
			||||||
 | 
						if newIndex >= int(this.countPieces) {
 | 
				
			||||||
 | 
							newIndex = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.gcPieceIndex = newIndex
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Cache) Destroy() {
 | 
				
			||||||
 | 
						this.isDestroyed = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if this.ticker != nil {
 | 
				
			||||||
 | 
							this.ticker.Stop()
 | 
				
			||||||
 | 
							this.ticker = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, piece := range this.pieces {
 | 
				
			||||||
 | 
							piece.Destroy()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										124
									
								
								internal/ttlcache/cache_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								internal/ttlcache/cache_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/rands"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNewCache(t *testing.T) {
 | 
				
			||||||
 | 
						cache := NewCache()
 | 
				
			||||||
 | 
						cache.Write("a", 1, time.Now().Unix()+3600)
 | 
				
			||||||
 | 
						cache.Write("b", 2, time.Now().Unix()+3601)
 | 
				
			||||||
 | 
						cache.Write("a", 1, time.Now().Unix()+3602)
 | 
				
			||||||
 | 
						cache.Write("d", 1, time.Now().Unix()+1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, piece := range cache.pieces {
 | 
				
			||||||
 | 
							if len(piece.m) > 0 {
 | 
				
			||||||
 | 
								for k, item := range piece.m {
 | 
				
			||||||
 | 
									t.Log(k, "=>", item.Value, item.expiredAt)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Log(cache.Read("a"))
 | 
				
			||||||
 | 
						time.Sleep(2 * time.Second)
 | 
				
			||||||
 | 
						t.Log(cache.Read("d"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkCache_Add(b *testing.B) {
 | 
				
			||||||
 | 
						runtime.GOMAXPROCS(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cache := NewCache()
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(i%1024))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCache_IncreaseInt64(t *testing.T) {
 | 
				
			||||||
 | 
						var cache = NewCache()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							cache.IncreaseInt64("a", 1, time.Now().Unix()+3600)
 | 
				
			||||||
 | 
							t.Log(cache.Read("a"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							cache.IncreaseInt64("a", 1, time.Now().Unix()+3600+1)
 | 
				
			||||||
 | 
							t.Log(cache.Read("a"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							cache.Write("b", 1, time.Now().Unix()+3600+2)
 | 
				
			||||||
 | 
							t.Log(cache.Read("b"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							cache.IncreaseInt64("b", 1, time.Now().Unix()+3600+3)
 | 
				
			||||||
 | 
							t.Log(cache.Read("b"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCache_Read(t *testing.T) {
 | 
				
			||||||
 | 
						runtime.GOMAXPROCS(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var cache = NewCache(PiecesOption{Count: 32})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < 10_000_000; i++ {
 | 
				
			||||||
 | 
							cache.Write("HELLO_WORLD_"+strconv.Itoa(i), i, time.Now().Unix()+int64(i%10240)+1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						total := 0
 | 
				
			||||||
 | 
						for _, piece := range cache.pieces {
 | 
				
			||||||
 | 
							//t.Log(len(piece.m), "keys")
 | 
				
			||||||
 | 
							total += len(piece.m)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Log(total, "total keys")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						before := time.Now()
 | 
				
			||||||
 | 
						for i := 0; i < 10_240; i++ {
 | 
				
			||||||
 | 
							_ = cache.Read("HELLO_WORLD_" + strconv.Itoa(i))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Log(time.Since(before).Seconds()*1000, "ms")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCache_GC(t *testing.T) {
 | 
				
			||||||
 | 
						var cache = NewCache(&PiecesOption{Count: 5})
 | 
				
			||||||
 | 
						cache.Write("a", 1, time.Now().Unix()+1)
 | 
				
			||||||
 | 
						cache.Write("b", 2, time.Now().Unix()+2)
 | 
				
			||||||
 | 
						cache.Write("c", 3, time.Now().Unix()+3)
 | 
				
			||||||
 | 
						cache.Write("d", 4, time.Now().Unix()+4)
 | 
				
			||||||
 | 
						cache.Write("e", 5, time.Now().Unix()+10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							for i := 0; i < 1000; i++ {
 | 
				
			||||||
 | 
								cache.Write("f", 1, time.Now().Unix()+1)
 | 
				
			||||||
 | 
								time.Sleep(10 * time.Millisecond)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < 20; i++ {
 | 
				
			||||||
 | 
							cache.GC()
 | 
				
			||||||
 | 
							t.Log("items:", cache.Count())
 | 
				
			||||||
 | 
							time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Log("now:", time.Now().Unix())
 | 
				
			||||||
 | 
						for _, p := range cache.pieces {
 | 
				
			||||||
 | 
							for k, v := range p.m {
 | 
				
			||||||
 | 
								t.Log(k, v.Value, v.expiredAt)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCache_GC2(t *testing.T) {
 | 
				
			||||||
 | 
						runtime.GOMAXPROCS(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cache := NewCache()
 | 
				
			||||||
 | 
						for i := 0; i < 1_000_000; i++ {
 | 
				
			||||||
 | 
							cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 100)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < 100; i++ {
 | 
				
			||||||
 | 
							t.Log(cache.Count(), "items")
 | 
				
			||||||
 | 
							time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								internal/ttlcache/item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/ttlcache/item.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Item struct {
 | 
				
			||||||
 | 
						Value     interface{}
 | 
				
			||||||
 | 
						expiredAt int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								internal/ttlcache/option.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								internal/ttlcache/option.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OptionInterface interface {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PiecesOption struct {
 | 
				
			||||||
 | 
						Count int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPiecesOption(count int) *PiecesOption {
 | 
				
			||||||
 | 
						return &PiecesOption{Count: count}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MaxItemsOption struct {
 | 
				
			||||||
 | 
						Count int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewMaxItemsOption(count int) *MaxItemsOption {
 | 
				
			||||||
 | 
						return &MaxItemsOption{Count: count}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								internal/ttlcache/piece.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/ttlcache/piece.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/types"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Piece struct {
 | 
				
			||||||
 | 
						m        map[uint64]*Item
 | 
				
			||||||
 | 
						maxItems int
 | 
				
			||||||
 | 
						locker   sync.RWMutex
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPiece(maxItems int) *Piece {
 | 
				
			||||||
 | 
						return &Piece{m: map[uint64]*Item{}, maxItems: maxItems}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) Add(key uint64, item *Item) () {
 | 
				
			||||||
 | 
						this.locker.Lock()
 | 
				
			||||||
 | 
						if len(this.m) >= this.maxItems {
 | 
				
			||||||
 | 
							this.locker.Unlock()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.m[key] = item
 | 
				
			||||||
 | 
						this.locker.Unlock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64) (result int64) {
 | 
				
			||||||
 | 
						this.locker.Lock()
 | 
				
			||||||
 | 
						item, ok := this.m[key]
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							result := types.Int64(item.Value) + delta
 | 
				
			||||||
 | 
							item.Value = result
 | 
				
			||||||
 | 
							item.expiredAt = expiredAt
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if len(this.m) < this.maxItems {
 | 
				
			||||||
 | 
								result = delta
 | 
				
			||||||
 | 
								this.m[key] = &Item{
 | 
				
			||||||
 | 
									Value:     delta,
 | 
				
			||||||
 | 
									expiredAt: expiredAt,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.locker.Unlock()
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) Delete(key uint64) {
 | 
				
			||||||
 | 
						this.locker.Lock()
 | 
				
			||||||
 | 
						delete(this.m, key)
 | 
				
			||||||
 | 
						this.locker.Unlock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) Read(key uint64) (item *Item) {
 | 
				
			||||||
 | 
						this.locker.RLock()
 | 
				
			||||||
 | 
						item = this.m[key]
 | 
				
			||||||
 | 
						if item != nil && item.expiredAt < time.Now().Unix() {
 | 
				
			||||||
 | 
							item = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.locker.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) Count() (count int) {
 | 
				
			||||||
 | 
						this.locker.RLock()
 | 
				
			||||||
 | 
						count = len(this.m)
 | 
				
			||||||
 | 
						this.locker.RUnlock()
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) GC() {
 | 
				
			||||||
 | 
						this.locker.Lock()
 | 
				
			||||||
 | 
						timestamp := time.Now().Unix()
 | 
				
			||||||
 | 
						for k, item := range this.m {
 | 
				
			||||||
 | 
							if item.expiredAt <= timestamp {
 | 
				
			||||||
 | 
								delete(this.m, k)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.locker.Unlock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *Piece) Destroy() {
 | 
				
			||||||
 | 
						this.locker.Lock()
 | 
				
			||||||
 | 
						this.m = nil
 | 
				
			||||||
 | 
						this.locker.Unlock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								internal/ttlcache/piece_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/ttlcache/piece_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/rands"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPiece_Add(t *testing.T) {
 | 
				
			||||||
 | 
						piece := NewPiece(10)
 | 
				
			||||||
 | 
						piece.Add(1, &Item{expiredAt: time.Now().Unix() + 3600})
 | 
				
			||||||
 | 
						piece.Add(2, &Item{})
 | 
				
			||||||
 | 
						piece.Add(3, &Item{})
 | 
				
			||||||
 | 
						piece.Delete(3)
 | 
				
			||||||
 | 
						for key, item := range piece.m {
 | 
				
			||||||
 | 
							t.Log(key, item.Value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Log(piece.Read(1))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPiece_MaxItems(t *testing.T) {
 | 
				
			||||||
 | 
						piece := NewPiece(10)
 | 
				
			||||||
 | 
						for i := 0; i < 1000; i++ {
 | 
				
			||||||
 | 
							piece.Add(uint64(i), &Item{expiredAt: time.Now().Unix() + 3600})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Log(len(piece.m))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPiece_GC(t *testing.T) {
 | 
				
			||||||
 | 
						piece := NewPiece(10)
 | 
				
			||||||
 | 
						piece.Add(1, &Item{Value: 1, expiredAt: time.Now().Unix() + 1})
 | 
				
			||||||
 | 
						piece.Add(2, &Item{Value: 2, expiredAt: time.Now().Unix() + 1})
 | 
				
			||||||
 | 
						piece.Add(3, &Item{Value: 3, expiredAt: time.Now().Unix() + 1})
 | 
				
			||||||
 | 
						t.Log("before gc ===")
 | 
				
			||||||
 | 
						for key, item := range piece.m {
 | 
				
			||||||
 | 
							t.Log(key, item.Value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
						piece.GC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Log("after gc ===")
 | 
				
			||||||
 | 
						for key, item := range piece.m {
 | 
				
			||||||
 | 
							t.Log(key, item.Value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPiece_GC2(t *testing.T) {
 | 
				
			||||||
 | 
						piece := NewPiece(10)
 | 
				
			||||||
 | 
						for i := 0; i < 10_000; i++ {
 | 
				
			||||||
 | 
							piece.Add(uint64(i), &Item{Value: 1, expiredAt: time.Now().Unix() + int64(rands.Int(1, 10))})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						before := time.Now()
 | 
				
			||||||
 | 
						piece.GC()
 | 
				
			||||||
 | 
						t.Log(time.Since(before).Seconds()*1000, "ms")
 | 
				
			||||||
 | 
						t.Log(piece.Count())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								internal/ttlcache/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								internal/ttlcache/utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "github.com/cespare/xxhash"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HashKey(key []byte) uint64 {
 | 
				
			||||||
 | 
						return xxhash.Sum64(key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								internal/ttlcache/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								internal/ttlcache/utils_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					package ttlcache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkHashKey(b *testing.B) {
 | 
				
			||||||
 | 
						runtime.GOMAXPROCS(1)
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							HashKey([]byte("HELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLD"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								internal/utils/ticker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								internal/utils/ticker.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 类似于time.Ticker,但能够真正地停止
 | 
				
			||||||
 | 
					type Ticker struct {
 | 
				
			||||||
 | 
						raw *time.Ticker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						S chan bool
 | 
				
			||||||
 | 
						C <-chan time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isStopped bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 创建新Ticker
 | 
				
			||||||
 | 
					func NewTicker(duration time.Duration) *Ticker {
 | 
				
			||||||
 | 
						raw := time.NewTicker(duration)
 | 
				
			||||||
 | 
						return &Ticker{
 | 
				
			||||||
 | 
							raw: raw,
 | 
				
			||||||
 | 
							C:   raw.C,
 | 
				
			||||||
 | 
							S:   make(chan bool, 1),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 查找下一个Tick
 | 
				
			||||||
 | 
					func (this *Ticker) Next() bool {
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-this.raw.C:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						case <-this.S:
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 停止
 | 
				
			||||||
 | 
					func (this *Ticker) Stop() {
 | 
				
			||||||
 | 
						if this.isStopped {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.isStopped = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.raw.Stop()
 | 
				
			||||||
 | 
						this.S <- true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,15 @@
 | 
				
			|||||||
package waf
 | 
					package waf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/models"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/actions"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/rands"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ExportAction struct {
 | 
					type ExportAction struct {
 | 
				
			||||||
	actionutils.ParentAction
 | 
						actionutils.ParentAction
 | 
				
			||||||
@@ -10,6 +19,102 @@ func (this *ExportAction) Init() {
 | 
				
			|||||||
	this.Nav("", "", "export")
 | 
						this.Nav("", "", "export")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (this *ExportAction) RunGet(params struct{}) {
 | 
					func (this *ExportAction) RunGet(params struct {
 | 
				
			||||||
 | 
						FirewallPolicyId int64
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
						policy, err := models.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyConfig(this.AdminContext(), params.FirewallPolicyId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if policy == nil {
 | 
				
			||||||
 | 
							this.NotFound("firewallPolicy", policy.Id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{}
 | 
				
			||||||
 | 
						outboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{}
 | 
				
			||||||
 | 
						if policy.Inbound != nil {
 | 
				
			||||||
 | 
							for _, g := range policy.Inbound.Groups {
 | 
				
			||||||
 | 
								if g.IsOn {
 | 
				
			||||||
 | 
									inboundGroups = append(inboundGroups, g)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if policy.Outbound != nil {
 | 
				
			||||||
 | 
							for _, g := range policy.Outbound.Groups {
 | 
				
			||||||
 | 
								if g.IsOn {
 | 
				
			||||||
 | 
									outboundGroups = append(outboundGroups, g)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.Data["inboundGroups"] = inboundGroups
 | 
				
			||||||
 | 
						this.Data["outboundGroups"] = outboundGroups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Show()
 | 
						this.Show()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *ExportAction) RunPost(params struct {
 | 
				
			||||||
 | 
						FirewallPolicyId int64
 | 
				
			||||||
 | 
						InboundGroupIds  []int64
 | 
				
			||||||
 | 
						OutboundGroupIds []int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Must *actions.Must
 | 
				
			||||||
 | 
						CSRF *actionutils.CSRF
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
						defer this.CreateLogInfo("导出WAF策略 %d", params.FirewallPolicyId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						policy, err := models.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyConfig(this.AdminContext(), params.FirewallPolicyId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if policy == nil {
 | 
				
			||||||
 | 
							this.NotFound("firewallPolicy", policy.Id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// inbound
 | 
				
			||||||
 | 
						newInboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{}
 | 
				
			||||||
 | 
						for _, inboundGroupId := range params.InboundGroupIds {
 | 
				
			||||||
 | 
							group := policy.FindRuleGroup(inboundGroupId)
 | 
				
			||||||
 | 
							if group != nil {
 | 
				
			||||||
 | 
								newInboundGroups = append(newInboundGroups, group)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if policy.Inbound == nil {
 | 
				
			||||||
 | 
							policy.Inbound = &firewallconfigs.HTTPFirewallInboundConfig{
 | 
				
			||||||
 | 
								IsOn: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						policy.Inbound.Groups = newInboundGroups
 | 
				
			||||||
 | 
						policy.Inbound.GroupRefs = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// outbound
 | 
				
			||||||
 | 
						newOutboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{}
 | 
				
			||||||
 | 
						for _, outboundGroupId := range params.OutboundGroupIds {
 | 
				
			||||||
 | 
							group := policy.FindRuleGroup(outboundGroupId)
 | 
				
			||||||
 | 
							if group != nil {
 | 
				
			||||||
 | 
								newOutboundGroups = append(newOutboundGroups, group)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if policy.Outbound == nil {
 | 
				
			||||||
 | 
							policy.Outbound = &firewallconfigs.HTTPFirewallOutboundConfig{
 | 
				
			||||||
 | 
								IsOn: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						policy.Outbound.Groups = newOutboundGroups
 | 
				
			||||||
 | 
						policy.Outbound.GroupRefs = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configJSON, err := json.Marshal(policy)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key := "waf." + rands.HexString(32)
 | 
				
			||||||
 | 
						ttlcache.DefaultCache.Write(key, configJSON, time.Now().Unix()+600)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.Data["key"] = key
 | 
				
			||||||
 | 
						this.Success()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					package waf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ExportDownloadAction struct {
 | 
				
			||||||
 | 
						actionutils.ParentAction
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *ExportDownloadAction) Init() {
 | 
				
			||||||
 | 
						this.Nav("", "", "")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *ExportDownloadAction) RunGet(params struct {
 | 
				
			||||||
 | 
						Key string
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
						item := ttlcache.DefaultCache.Read(params.Key)
 | 
				
			||||||
 | 
						if item == nil || item.Value == nil {
 | 
				
			||||||
 | 
							this.WriteString("找不到要导出的内容")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ttlcache.DefaultCache.Delete(params.Key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, ok := item.Value.([]byte)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							this.AddHeader("Content-Disposition", "attachment; filename=\"WAF.json\";")
 | 
				
			||||||
 | 
							this.AddHeader("Content-Length", strconv.Itoa(len(data)))
 | 
				
			||||||
 | 
							this.Write(data)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this.WriteString("找不到要导出的内容")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,12 @@
 | 
				
			|||||||
package waf
 | 
					package waf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/actions"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ImportAction struct {
 | 
					type ImportAction struct {
 | 
				
			||||||
	actionutils.ParentAction
 | 
						actionutils.ParentAction
 | 
				
			||||||
@@ -13,3 +19,41 @@ func (this *ImportAction) Init() {
 | 
				
			|||||||
func (this *ImportAction) RunGet(params struct{}) {
 | 
					func (this *ImportAction) RunGet(params struct{}) {
 | 
				
			||||||
	this.Show()
 | 
						this.Show()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *ImportAction) RunPost(params struct {
 | 
				
			||||||
 | 
						FirewallPolicyId int64
 | 
				
			||||||
 | 
						File             *actions.File
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Must *actions.Must
 | 
				
			||||||
 | 
						CSRF *actionutils.CSRF
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
						defer this.CreateLogInfo("从文件中导入规则到WAF策略 %d", params.FirewallPolicyId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if params.File == nil {
 | 
				
			||||||
 | 
							this.Fail("请上传要导入的文件")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if params.File.Ext != ".json" {
 | 
				
			||||||
 | 
							this.Fail("规则文件的扩展名只能是.json")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := params.File.Read()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.Fail("读取文件时发生错误:" + err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := &firewallconfigs.HTTPFirewallPolicy{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(data, config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.Fail("解析文件时发生错误:" + err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = this.RPC().HTTPFirewallPolicyRPC().ImportHTTPFirewallPolicy(this.AdminContext(), &pb.ImportHTTPFirewallPolicyRequest{
 | 
				
			||||||
 | 
							FirewallPolicyId:   params.FirewallPolicyId,
 | 
				
			||||||
 | 
							FirewallPolicyJSON: data,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.Fail("导入失败:" + err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.Success()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@ func init() {
 | 
				
			|||||||
			GetPost("/update", new(UpdateAction)).
 | 
								GetPost("/update", new(UpdateAction)).
 | 
				
			||||||
			GetPost("/test", new(TestAction)).
 | 
								GetPost("/test", new(TestAction)).
 | 
				
			||||||
			GetPost("/export", new(ExportAction)).
 | 
								GetPost("/export", new(ExportAction)).
 | 
				
			||||||
 | 
								Get("/exportDownload", new(ExportDownloadAction)).
 | 
				
			||||||
			GetPost("/import", new(ImportAction)).
 | 
								GetPost("/import", new(ImportAction)).
 | 
				
			||||||
			Post("/updateGroupOn", new(UpdateGroupOnAction)).
 | 
								Post("/updateGroupOn", new(UpdateGroupOnAction)).
 | 
				
			||||||
			Post("/deleteGroup", new(DeleteGroupAction)).
 | 
								Post("/deleteGroup", new(DeleteGroupAction)).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@
 | 
				
			|||||||
	<menu-item :href="'/servers/components/waf/ipadmin?firewallPolicyId=' + firewallPolicyId" code="ipadmin">IP管理</menu-item>
 | 
						<menu-item :href="'/servers/components/waf/ipadmin?firewallPolicyId=' + firewallPolicyId" code="ipadmin">IP管理</menu-item>
 | 
				
			||||||
	<menu-item :href="'/servers/components/waf/log?firewallPolicyId=' + firewallPolicyId" code="log">拦截日志</menu-item>
 | 
						<menu-item :href="'/servers/components/waf/log?firewallPolicyId=' + firewallPolicyId" code="log">拦截日志</menu-item>
 | 
				
			||||||
	<!-- TODO -->
 | 
						<!-- TODO -->
 | 
				
			||||||
	<!--<menu-item :href="'/servers/components/waf/test?firewallPolicyId=' + firewallPolicyId" code="test">测试</menu-item>
 | 
						<!--<menu-item :href="'/servers/components/waf/test?firewallPolicyId=' + firewallPolicyId" code="test">测试</menu-item>-->
 | 
				
			||||||
	<menu-item :href="'/servers/components/waf/import?firewallPolicyId=' + firewallPolicyId" code="import">导入</menu-item>
 | 
						<menu-item :href="'/servers/components/waf/import?firewallPolicyId=' + firewallPolicyId" code="import">导入</menu-item>
 | 
				
			||||||
	<menu-item :href="'/servers/components/waf/export?firewallPolicyId=' + firewallPolicyId" code="export">导出</menu-item>-->
 | 
						<menu-item :href="'/servers/components/waf/export?firewallPolicyId=' + firewallPolicyId" code="export">导出</menu-item>
 | 
				
			||||||
	<menu-item :href="'/servers/components/waf/update?firewallPolicyId=' + firewallPolicyId" code="update">修改</menu-item>
 | 
						<menu-item :href="'/servers/components/waf/update?firewallPolicyId=' + firewallPolicyId" code="update">修改</menu-item>
 | 
				
			||||||
</second-menu>
 | 
					</second-menu>
 | 
				
			||||||
							
								
								
									
										7
									
								
								web/views/@default/servers/components/waf/export.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								web/views/@default/servers/components/waf/export.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					.groups-box .group-box {
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					  width: 11em;
 | 
				
			||||||
 | 
					  margin-top: 0.1em;
 | 
				
			||||||
 | 
					  margin-bottom: 0.6em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/*# sourceMappingURL=export.css.map */
 | 
				
			||||||
							
								
								
									
										1
									
								
								web/views/@default/servers/components/waf/export.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/servers/components/waf/export.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"version":3,"sources":["export.less"],"names":[],"mappings":"AAAA,WACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"export.css"}
 | 
				
			||||||
@@ -1,5 +1,33 @@
 | 
				
			|||||||
{$layout}
 | 
					{$layout}
 | 
				
			||||||
 | 
					{$template "waf_menu"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{$template "waf_menu"}
 | 
					<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
 | 
				
			||||||
 | 
						<csrf-token></csrf-token>
 | 
				
			||||||
 | 
						<input type="hidden" name="firewallPolicyId" :value="firewallPolicyId"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<p class="ui message">此功能暂未开放,敬请期待。</p>
 | 
						<table class="ui table definition selectable">
 | 
				
			||||||
 | 
							<tr>
 | 
				
			||||||
 | 
								<td class="title">选择入站规则</td>
 | 
				
			||||||
 | 
								<td>
 | 
				
			||||||
 | 
									<span v-if="inboundGroups.length == 0" class="disabled">暂时还没有入站规则。</span>
 | 
				
			||||||
 | 
									<div class="groups-box" v-show="inboundGroups.length > 0">
 | 
				
			||||||
 | 
										<div v-for="g in inboundGroups" class="group-box">
 | 
				
			||||||
 | 
											<checkbox name="inboundGroupIds" :value="true" :v-value="g.id">{{g.name}}</checkbox>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</td>
 | 
				
			||||||
 | 
							</tr>
 | 
				
			||||||
 | 
							<tr>
 | 
				
			||||||
 | 
								<td>选择出站规则</td>
 | 
				
			||||||
 | 
								<td>
 | 
				
			||||||
 | 
									<span v-if="outboundGroups.length == 0" class="disabled">暂时还没有出站规则。</span>
 | 
				
			||||||
 | 
									<div class="groups-box" v-show="outboundGroups.length > 0">
 | 
				
			||||||
 | 
										<div v-for="g in outboundGroups" class="group-box">
 | 
				
			||||||
 | 
											<checkbox name="outboundGroupIds" :value="true" :v-value="g.id">{{g.name}}</checkbox>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</td>
 | 
				
			||||||
 | 
							</tr>
 | 
				
			||||||
 | 
						</table>
 | 
				
			||||||
 | 
						<submit-btn>导出</submit-btn>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
							
								
								
									
										5
									
								
								web/views/@default/servers/components/waf/export.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/views/@default/servers/components/waf/export.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					Tea.context(function () {
 | 
				
			||||||
 | 
						this.success = function (resp) {
 | 
				
			||||||
 | 
							window.location = "/servers/components/waf/exportDownload?key=" + resp.data.key
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										8
									
								
								web/views/@default/servers/components/waf/export.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								web/views/@default/servers/components/waf/export.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					.groups-box {
 | 
				
			||||||
 | 
						.group-box {
 | 
				
			||||||
 | 
							float: left;
 | 
				
			||||||
 | 
							width: 11em;
 | 
				
			||||||
 | 
							margin-top: 0.1em;
 | 
				
			||||||
 | 
							margin-bottom: 0.6em;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,16 @@
 | 
				
			|||||||
{$layout}
 | 
					{$layout}
 | 
				
			||||||
 | 
					{$template "waf_menu"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{$template "waf_menu"}
 | 
					<form method="post" class="ui form" data-tea-success="success" data-tea-action="$">
 | 
				
			||||||
 | 
						<csrf-token></csrf-token>
 | 
				
			||||||
	<p class="ui message">此功能暂未开放,敬请期待。</p>
 | 
						<input type="hidden" name="firewallPolicyId" :value="firewallPolicyId"/>
 | 
				
			||||||
 | 
						<table class="ui table definition selectable">
 | 
				
			||||||
 | 
							<tr>
 | 
				
			||||||
 | 
								<td class="title">选择要倒入的规则文件</td>
 | 
				
			||||||
 | 
								<td>
 | 
				
			||||||
 | 
									<input type="file" name="file" accept=".json"/>
 | 
				
			||||||
 | 
								</td>
 | 
				
			||||||
 | 
							</tr>
 | 
				
			||||||
 | 
						</table>
 | 
				
			||||||
 | 
						<submit-btn>导入</submit-btn>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
							
								
								
									
										3
									
								
								web/views/@default/servers/components/waf/import.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/views/@default/servers/components/waf/import.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					Tea.context(function () {
 | 
				
			||||||
 | 
						this.success = NotifyReloadSuccess("导入成功")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Reference in New Issue
	
	Block a user