github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/trafficlimit/traffic_limit.go (about)

     1  package trafficlimit
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/go-co-op/gocron/v2"
     8  
     9  	confid "github.com/machinefi/w3bstream/pkg/depends/conf/id"
    10  	"github.com/machinefi/w3bstream/pkg/depends/kit/logr"
    11  	"github.com/machinefi/w3bstream/pkg/depends/kit/sqlx"
    12  	"github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/builder"
    13  	"github.com/machinefi/w3bstream/pkg/enums"
    14  	"github.com/machinefi/w3bstream/pkg/errors/status"
    15  	"github.com/machinefi/w3bstream/pkg/models"
    16  	"github.com/machinefi/w3bstream/pkg/types"
    17  )
    18  
    19  var prefix = "traffic_limit"
    20  
    21  func Init(ctx context.Context) error {
    22  	_, l := logr.Start(ctx, "trafficLimit.Init")
    23  	defer l.End()
    24  
    25  	kv := types.MustRedisEndpointFromContext(ctx).WithPrefix(prefix)
    26  	d := types.MustMgrDBExecutorFromContext(ctx)
    27  
    28  	if !types.EnableTrafficLimitFromContext(ctx) {
    29  		return nil
    30  	}
    31  
    32  	keys, _ := kv.Keys("*")
    33  	_ = kv.RawDel(keys...)
    34  
    35  	s, err := gocron.NewScheduler()
    36  	if err != nil {
    37  		return err
    38  	}
    39  	_, err = s.NewJob(gocron.DurationJob(time.Minute), gocron.NewTask(func(d sqlx.DBExecutor) error {
    40  		list, err := (&models.TrafficLimit{}).List(d, nil)
    41  		if err != nil {
    42  			return err
    43  		}
    44  
    45  		ids := make(map[types.SFID]int)
    46  		for i := range list {
    47  			ids[list[i].TrafficLimitID] = i
    48  		}
    49  
    50  		for _, i := range ids {
    51  			_ = AddAndStartScheduler(ctx, &list[i])
    52  		}
    53  
    54  		keys := make(map[types.SFID]struct{})
    55  		schedulers.Range(func(k types.SFID, v *Scheduler) bool {
    56  			keys[k] = struct{}{}
    57  			return true
    58  		})
    59  
    60  		for id, _ := range keys {
    61  			if _, ok := ids[id]; !ok {
    62  				RmvScheduler(ctx, id)
    63  			}
    64  		}
    65  
    66  		return nil
    67  	}, d))
    68  	if err != nil {
    69  		return err
    70  	}
    71  	s.Start()
    72  	return nil
    73  }
    74  
    75  func GetBySFID(ctx context.Context, id types.SFID) (*models.TrafficLimit, error) {
    76  	d := types.MustMgrDBExecutorFromContext(ctx)
    77  	m := &models.TrafficLimit{RelTrafficLimit: models.RelTrafficLimit{TrafficLimitID: id}}
    78  
    79  	if err := m.FetchByTrafficLimitID(d); err != nil {
    80  		if sqlx.DBErr(err).IsNotFound() {
    81  			return nil, status.TrafficLimitNotFound
    82  		}
    83  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
    84  	}
    85  	return m, nil
    86  }
    87  
    88  func Create(ctx context.Context, r *CreateReq) (*models.TrafficLimit, error) {
    89  	var (
    90  		d   = types.MustMgrDBExecutorFromContext(ctx)
    91  		idg = confid.MustSFIDGeneratorFromContext(ctx)
    92  		prj = types.MustProjectFromContext(ctx)
    93  	)
    94  
    95  	m := &models.TrafficLimit{
    96  		RelTrafficLimit: models.RelTrafficLimit{TrafficLimitID: idg.MustGenSFID()},
    97  		RelProject:      models.RelProject{ProjectID: prj.ProjectID},
    98  		TrafficLimitInfo: models.TrafficLimitInfo{
    99  			Threshold: r.Threshold,
   100  			Duration:  r.Duration,
   101  			ApiType:   r.ApiType,
   102  			StartAt:   types.Timestamp{Time: time.Now()},
   103  		},
   104  	}
   105  
   106  	if err := m.Create(d); err != nil {
   107  		if sqlx.DBErr(err).IsConflict() {
   108  			return nil, status.TrafficLimitConflict
   109  		}
   110  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   111  	}
   112  	return m, nil
   113  }
   114  
   115  func Update(ctx context.Context, id types.SFID, r *UpdateReq) (*models.TrafficLimit, error) {
   116  	var (
   117  		d   = types.MustMgrDBExecutorFromContext(ctx)
   118  		m   *models.TrafficLimit
   119  		err error
   120  	)
   121  
   122  	err = sqlx.NewTasks(d).With(
   123  		func(db sqlx.DBExecutor) error {
   124  			m, err = GetBySFID(ctx, id)
   125  			if err != nil {
   126  				return err
   127  			}
   128  			return nil
   129  		},
   130  		func(db sqlx.DBExecutor) error {
   131  			m.TrafficLimitID = id
   132  			m.Threshold = r.Threshold
   133  			m.Duration = r.Duration
   134  			if err = m.UpdateByTrafficLimitID(d); err != nil {
   135  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
   136  			}
   137  			return nil
   138  		},
   139  	).Do()
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return m, nil
   145  }
   146  
   147  func List(ctx context.Context, r *ListReq) (*ListRsp, error) {
   148  	var (
   149  		d       = types.MustMgrDBExecutorFromContext(ctx)
   150  		traffic = &models.TrafficLimit{}
   151  		ret     = &ListRsp{}
   152  		cond    = r.Condition()
   153  
   154  		err error
   155  	)
   156  
   157  	if ret.Data, err = traffic.List(d, cond, r.Addition()); err != nil {
   158  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   159  	}
   160  	if ret.Total, err = traffic.Count(d, cond); err != nil {
   161  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   162  	}
   163  	return ret, nil
   164  }
   165  
   166  func ListByCond(ctx context.Context, r *CondArgs) (data []models.TrafficLimit, err error) {
   167  	var (
   168  		d = types.MustMgrDBExecutorFromContext(ctx)
   169  		m = &models.TrafficLimit{}
   170  	)
   171  	data, err = m.List(d, r.Condition())
   172  	if err != nil {
   173  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   174  	}
   175  	return data, nil
   176  }
   177  
   178  func ListDetail(ctx context.Context, r *ListReq) (*ListDetailRsp, error) {
   179  	var (
   180  		d = types.MustMgrDBExecutorFromContext(ctx)
   181  
   182  		rate = &models.TrafficLimit{}
   183  		prj  = types.MustProjectFromContext(ctx)
   184  		ret  = &ListDetailRsp{}
   185  		err  error
   186  
   187  		cond = r.Condition()
   188  		adds = r.Additions()
   189  	)
   190  
   191  	expr := builder.Select(builder.MultiWith(",",
   192  		builder.Alias(prj.ColName(), "f_project_name"),
   193  		builder.Alias(rate.ColProjectID(), "f_project_id"),
   194  		builder.Alias(rate.ColTrafficLimitID(), "f_traffic_limit_id"),
   195  		builder.Alias(rate.ColThreshold(), "f_threshold"),
   196  		builder.Alias(rate.ColDuration(), "f_duration"),
   197  		builder.Alias(rate.ColApiType(), "f_api_type"),
   198  		builder.Alias(rate.ColCreatedAt(), "f_created_at"),
   199  		builder.Alias(rate.ColUpdatedAt(), "f_updated_at"),
   200  	)).From(
   201  		d.T(rate),
   202  		append([]builder.Addition{
   203  			builder.LeftJoin(d.T(prj)).On(rate.ColProjectID().Eq(prj.ColProjectID())),
   204  			builder.Where(cond),
   205  		}, adds...)...,
   206  	)
   207  	err = d.QueryAndScan(expr, &ret.Data)
   208  	if err != nil {
   209  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   210  	}
   211  	ret.Total, err = rate.Count(d, cond)
   212  	if err != nil {
   213  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   214  	}
   215  	return ret, nil
   216  }
   217  
   218  func RemoveBySFID(ctx context.Context, id types.SFID) error {
   219  	var (
   220  		d = types.MustMgrDBExecutorFromContext(ctx)
   221  		m = &models.TrafficLimit{}
   222  	)
   223  
   224  	m.TrafficLimitID = id
   225  	if err := m.DeleteByTrafficLimitID(d); err != nil {
   226  		return status.DatabaseError.StatusErr().WithDesc(err.Error())
   227  	}
   228  	return nil
   229  }
   230  
   231  func Remove(ctx context.Context, r *CondArgs) error {
   232  	var (
   233  		d = types.MustMgrDBExecutorFromContext(ctx)
   234  		m = &models.TrafficLimit{}
   235  
   236  		err error
   237  	)
   238  
   239  	if r.Condition().IsNil() {
   240  		return status.InvalidDeleteCondition
   241  	}
   242  
   243  	return sqlx.NewTasks(d).With(
   244  		func(d sqlx.DBExecutor) error {
   245  			_, err = ListDetail(ctx, &ListReq{CondArgs: *r})
   246  			if err != nil {
   247  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
   248  			}
   249  			return nil
   250  		},
   251  		func(d sqlx.DBExecutor) error {
   252  			expr := builder.Delete().From(d.T(m), builder.Where(r.Condition()))
   253  			_, err = d.Exec(expr)
   254  			if err != nil {
   255  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
   256  			}
   257  			return nil
   258  		},
   259  	).Do()
   260  }
   261  
   262  func TrafficLimit(ctx context.Context, prj types.SFID, tpe enums.TrafficLimitType) error {
   263  	ctx, l := logr.Start(ctx, "trafficLimit.TrafficLimit")
   264  	defer l.End()
   265  
   266  	r := types.MustRedisEndpointFromContext(ctx)
   267  
   268  	// for statistics per hour
   269  	stat := r.WithPrefix("stat" + ":" + prj.String())
   270  	if total, err := stat.IncrBy(time.Now().Format("2006010215"), 1); err != nil {
   271  		l.WithValues("prj", prj, "total", total).Info("")
   272  	}
   273  
   274  	// for traffic limit
   275  	limit := r.WithPrefix(prefix)
   276  	l = l.WithValues("prj", prj, "tpe", tpe)
   277  	m := &models.TrafficLimit{
   278  		RelProject:       models.RelProject{ProjectID: prj},
   279  		TrafficLimitInfo: models.TrafficLimitInfo{ApiType: tpe},
   280  	}
   281  
   282  	exists, _ := limit.Exists(m.CacheKey())
   283  	if !exists {
   284  		l.Info("no strategy")
   285  		return nil
   286  	}
   287  
   288  	count, _ := limit.IncrBy(m.CacheKey(), -1)
   289  	if count <= 0 {
   290  		l.Info("limited")
   291  		return status.TrafficLimitExceededFailed
   292  	}
   293  
   294  	l.WithValues("remain", count).Info("")
   295  	return nil
   296  }