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 }